Implement -monoMouse option (XB)
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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 StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <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
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #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"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5145                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5146                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5148           SendToProgram(buf, cps);
5149       } else
5150       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5151         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5152           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5153           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5154                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5155         } else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5157                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5158         SendToProgram(buf, cps);
5159       }
5160       else SendToProgram(moveList[moveNum], cps);
5161       /* End of additions by Tord */
5162     }
5163
5164     /* [HGM] setting up the opening has brought engine in force mode! */
5165     /*       Send 'go' if we are in a mode where machine should play. */
5166     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5167         (gameMode == TwoMachinesPlay   ||
5168 #if ZIPPY
5169          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5170 #endif
5171          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5172         SendToProgram("go\n", cps);
5173   if (appData.debugMode) {
5174     fprintf(debugFP, "(extra)\n");
5175   }
5176     }
5177     setboardSpoiledMachineBlack = 0;
5178 }
5179
5180 void
5181 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5182 {
5183     char user_move[MSG_SIZ];
5184     char suffix[4];
5185
5186     if(gameInfo.variant == VariantSChess && promoChar) {
5187         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5188         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5189     } else suffix[0] = NULLCHAR;
5190
5191     switch (moveType) {
5192       default:
5193         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5194                 (int)moveType, fromX, fromY, toX, toY);
5195         DisplayError(user_move + strlen("say "), 0);
5196         break;
5197       case WhiteKingSideCastle:
5198       case BlackKingSideCastle:
5199       case WhiteQueenSideCastleWild:
5200       case BlackQueenSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteHSideCastleFR:
5203       case BlackHSideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5206         break;
5207       case WhiteQueenSideCastle:
5208       case BlackQueenSideCastle:
5209       case WhiteKingSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteASideCastleFR:
5213       case BlackASideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5216         break;
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221       case WhitePromotion:
5222       case BlackPromotion:
5223         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5224            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5225           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteFerz));
5228         else if(gameInfo.variant == VariantGreat)
5229           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 PieceToChar(WhiteMan));
5232         else
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 promoChar);
5236         break;
5237       case WhiteDrop:
5238       case BlackDrop:
5239       drop:
5240         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5241                  ToUpper(PieceToChar((ChessSquare) fromX)),
5242                  AAA + toX, ONE + toY);
5243         break;
5244       case IllegalMove:  /* could be a variant we don't quite understand */
5245         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5246       case NormalMove:
5247       case WhiteCapturesEnPassant:
5248       case BlackCapturesEnPassant:
5249         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5251         break;
5252     }
5253     SendToICS(user_move);
5254     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5255         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5256 }
5257
5258 void
5259 UploadGameEvent ()
5260 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5261     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5262     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5263     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5264       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5265       return;
5266     }
5267     if(gameMode != IcsExamining) { // is this ever not the case?
5268         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5269
5270         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5271           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5272         } else { // on FICS we must first go to general examine mode
5273           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5274         }
5275         if(gameInfo.variant != VariantNormal) {
5276             // try figure out wild number, as xboard names are not always valid on ICS
5277             for(i=1; i<=36; i++) {
5278               snprintf(buf, MSG_SIZ, "wild/%d", i);
5279                 if(StringToVariant(buf) == gameInfo.variant) break;
5280             }
5281             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5282             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5283             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5284         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5285         SendToICS(ics_prefix);
5286         SendToICS(buf);
5287         if(startedFromSetupPosition || backwardMostMove != 0) {
5288           fen = PositionToFEN(backwardMostMove, NULL, 1);
5289           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5290             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5291             SendToICS(buf);
5292           } else { // FICS: everything has to set by separate bsetup commands
5293             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5294             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5295             SendToICS(buf);
5296             if(!WhiteOnMove(backwardMostMove)) {
5297                 SendToICS("bsetup tomove black\n");
5298             }
5299             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5300             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5301             SendToICS(buf);
5302             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5303             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5304             SendToICS(buf);
5305             i = boards[backwardMostMove][EP_STATUS];
5306             if(i >= 0) { // set e.p.
5307               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5308                 SendToICS(buf);
5309             }
5310             bsetup++;
5311           }
5312         }
5313       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5314             SendToICS("bsetup done\n"); // switch to normal examining.
5315     }
5316     for(i = backwardMostMove; i<last; i++) {
5317         char buf[20];
5318         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5319         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5320             int len = strlen(moveList[i]);
5321             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5322             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5323         }
5324         SendToICS(buf);
5325     }
5326     SendToICS(ics_prefix);
5327     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5328 }
5329
5330 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5331 int legNr = 1;
5332
5333 void
5334 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5335 {
5336     if (rf == DROP_RANK) {
5337       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5338       sprintf(move, "%c@%c%c\n",
5339                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5340     } else {
5341         if (promoChar == 'x' || promoChar == NULLCHAR) {
5342           sprintf(move, "%c%c%c%c\n",
5343                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5344           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5345         } else {
5346             sprintf(move, "%c%c%c%c%c\n",
5347                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5348         }
5349     }
5350 }
5351
5352 void
5353 ProcessICSInitScript (FILE *f)
5354 {
5355     char buf[MSG_SIZ];
5356
5357     while (fgets(buf, MSG_SIZ, f)) {
5358         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5359     }
5360
5361     fclose(f);
5362 }
5363
5364
5365 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5366 int dragging;
5367 static ClickType lastClickType;
5368
5369 int
5370 Partner (ChessSquare *p)
5371 { // change piece into promotion partner if one shogi-promotes to the other
5372   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5373   ChessSquare partner;
5374   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5375   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5376   *p = partner;
5377   return 1;
5378 }
5379
5380 void
5381 Sweep (int step)
5382 {
5383     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5384     static int toggleFlag;
5385     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5386     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5387     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5388     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5389     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5390     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5391     do {
5392         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5393         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5394         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5395         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5396         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5397         if(!step) step = -1;
5398     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5399             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5400             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5401             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5402     if(toX >= 0) {
5403         int victim = boards[currentMove][toY][toX];
5404         boards[currentMove][toY][toX] = promoSweep;
5405         DrawPosition(FALSE, boards[currentMove]);
5406         boards[currentMove][toY][toX] = victim;
5407     } else
5408     ChangeDragPiece(promoSweep);
5409 }
5410
5411 int
5412 PromoScroll (int x, int y)
5413 {
5414   int step = 0;
5415
5416   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5417   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5418   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5419   if(!step) return FALSE;
5420   lastX = x; lastY = y;
5421   if((promoSweep < BlackPawn) == flipView) step = -step;
5422   if(step > 0) selectFlag = 1;
5423   if(!selectFlag) Sweep(step);
5424   return FALSE;
5425 }
5426
5427 void
5428 NextPiece (int step)
5429 {
5430     ChessSquare piece = boards[currentMove][toY][toX];
5431     do {
5432         pieceSweep -= step;
5433         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5434         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(pieceSweep) == '.');
5437     boards[currentMove][toY][toX] = pieceSweep;
5438     DrawPosition(FALSE, boards[currentMove]);
5439     boards[currentMove][toY][toX] = piece;
5440 }
5441 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5442 void
5443 AlphaRank (char *move, int n)
5444 {
5445 //    char *p = move, c; int x, y;
5446
5447     if (appData.debugMode) {
5448         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5449     }
5450
5451     if(move[1]=='*' &&
5452        move[2]>='0' && move[2]<='9' &&
5453        move[3]>='a' && move[3]<='x'    ) {
5454         move[1] = '@';
5455         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5456         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5457     } else
5458     if(move[0]>='0' && move[0]<='9' &&
5459        move[1]>='a' && move[1]<='x' &&
5460        move[2]>='0' && move[2]<='9' &&
5461        move[3]>='a' && move[3]<='x'    ) {
5462         /* input move, Shogi -> normal */
5463         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5464         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[1]=='@' &&
5469        move[3]>='0' && move[3]<='9' &&
5470        move[2]>='a' && move[2]<='x'    ) {
5471         move[1] = '*';
5472         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5474     } else
5475     if(
5476        move[0]>='a' && move[0]<='x' &&
5477        move[3]>='0' && move[3]<='9' &&
5478        move[2]>='a' && move[2]<='x'    ) {
5479          /* output move, normal -> Shogi */
5480         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5481         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5485     }
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "   out = '%s'\n", move);
5488     }
5489 }
5490
5491 char yy_textstr[8000];
5492
5493 /* Parser for moves from gnuchess, ICS, or user typein box */
5494 Boolean
5495 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5496 {
5497     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5498
5499     switch (*moveType) {
5500       case WhitePromotion:
5501       case BlackPromotion:
5502       case WhiteNonPromotion:
5503       case BlackNonPromotion:
5504       case NormalMove:
5505       case FirstLeg:
5506       case WhiteCapturesEnPassant:
5507       case BlackCapturesEnPassant:
5508       case WhiteKingSideCastle:
5509       case WhiteQueenSideCastle:
5510       case BlackKingSideCastle:
5511       case BlackQueenSideCastle:
5512       case WhiteKingSideCastleWild:
5513       case WhiteQueenSideCastleWild:
5514       case BlackKingSideCastleWild:
5515       case BlackQueenSideCastleWild:
5516       /* Code added by Tord: */
5517       case WhiteHSideCastleFR:
5518       case WhiteASideCastleFR:
5519       case BlackHSideCastleFR:
5520       case BlackASideCastleFR:
5521       /* End of code added by Tord */
5522       case IllegalMove:         /* bug or odd chess variant */
5523         *fromX = currentMoveString[0] - AAA;
5524         *fromY = currentMoveString[1] - ONE;
5525         *toX = currentMoveString[2] - AAA;
5526         *toY = currentMoveString[3] - ONE;
5527         *promoChar = currentMoveString[4];
5528         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5529             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5530     if (appData.debugMode) {
5531         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5532     }
5533             *fromX = *fromY = *toX = *toY = 0;
5534             return FALSE;
5535         }
5536         if (appData.testLegality) {
5537           return (*moveType != IllegalMove);
5538         } else {
5539           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5540                          // [HGM] lion: if this is a double move we are less critical
5541                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5542         }
5543
5544       case WhiteDrop:
5545       case BlackDrop:
5546         *fromX = *moveType == WhiteDrop ?
5547           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5548           (int) CharToPiece(ToLower(currentMoveString[0]));
5549         *fromY = DROP_RANK;
5550         *toX = currentMoveString[2] - AAA;
5551         *toY = currentMoveString[3] - ONE;
5552         *promoChar = NULLCHAR;
5553         return TRUE;
5554
5555       case AmbiguousMove:
5556       case ImpossibleMove:
5557       case EndOfFile:
5558       case ElapsedTime:
5559       case Comment:
5560       case PGNTag:
5561       case NAG:
5562       case WhiteWins:
5563       case BlackWins:
5564       case GameIsDrawn:
5565       default:
5566     if (appData.debugMode) {
5567         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5568     }
5569         /* bug? */
5570         *fromX = *fromY = *toX = *toY = 0;
5571         *promoChar = NULLCHAR;
5572         return FALSE;
5573     }
5574 }
5575
5576 Boolean pushed = FALSE;
5577 char *lastParseAttempt;
5578
5579 void
5580 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5581 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5582   int fromX, fromY, toX, toY; char promoChar;
5583   ChessMove moveType;
5584   Boolean valid;
5585   int nr = 0;
5586
5587   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5588   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5589     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5590     pushed = TRUE;
5591   }
5592   endPV = forwardMostMove;
5593   do {
5594     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5595     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5596     lastParseAttempt = pv;
5597     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5598     if(!valid && nr == 0 &&
5599        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5600         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5601         // Hande case where played move is different from leading PV move
5602         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5603         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5604         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5605         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5606           endPV += 2; // if position different, keep this
5607           moveList[endPV-1][0] = fromX + AAA;
5608           moveList[endPV-1][1] = fromY + ONE;
5609           moveList[endPV-1][2] = toX + AAA;
5610           moveList[endPV-1][3] = toY + ONE;
5611           parseList[endPV-1][0] = NULLCHAR;
5612           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5613         }
5614       }
5615     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5616     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5617     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5618     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5619         valid++; // allow comments in PV
5620         continue;
5621     }
5622     nr++;
5623     if(endPV+1 > framePtr) break; // no space, truncate
5624     if(!valid) break;
5625     endPV++;
5626     CopyBoard(boards[endPV], boards[endPV-1]);
5627     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5628     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5629     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5630     CoordsToAlgebraic(boards[endPV - 1],
5631                              PosFlags(endPV - 1),
5632                              fromY, fromX, toY, toX, promoChar,
5633                              parseList[endPV - 1]);
5634   } while(valid);
5635   if(atEnd == 2) return; // used hidden, for PV conversion
5636   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5637   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5638   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5639                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5640   DrawPosition(TRUE, boards[currentMove]);
5641 }
5642
5643 int
5644 MultiPV (ChessProgramState *cps)
5645 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5646         int i;
5647         for(i=0; i<cps->nrOptions; i++)
5648             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5649                 return i;
5650         return -1;
5651 }
5652
5653 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5654
5655 Boolean
5656 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5657 {
5658         int startPV, multi, lineStart, origIndex = index;
5659         char *p, buf2[MSG_SIZ];
5660         ChessProgramState *cps = (pane ? &second : &first);
5661
5662         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5663         lastX = x; lastY = y;
5664         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5665         lineStart = startPV = index;
5666         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5667         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5668         index = startPV;
5669         do{ while(buf[index] && buf[index] != '\n') index++;
5670         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5671         buf[index] = 0;
5672         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5673                 int n = cps->option[multi].value;
5674                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5675                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5676                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5677                 cps->option[multi].value = n;
5678                 *start = *end = 0;
5679                 return FALSE;
5680         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5681                 ExcludeClick(origIndex - lineStart);
5682                 return FALSE;
5683         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5684                 Collapse(origIndex - lineStart);
5685                 return FALSE;
5686         }
5687         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5688         *start = startPV; *end = index-1;
5689         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5690         return TRUE;
5691 }
5692
5693 char *
5694 PvToSAN (char *pv)
5695 {
5696         static char buf[10*MSG_SIZ];
5697         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5698         *buf = NULLCHAR;
5699         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5700         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5701         for(i = forwardMostMove; i<endPV; i++){
5702             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5703             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5704             k += strlen(buf+k);
5705         }
5706         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5707         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5708         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5709         endPV = savedEnd;
5710         return buf;
5711 }
5712
5713 Boolean
5714 LoadPV (int x, int y)
5715 { // called on right mouse click to load PV
5716   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5717   lastX = x; lastY = y;
5718   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5719   extendGame = FALSE;
5720   return TRUE;
5721 }
5722
5723 void
5724 UnLoadPV ()
5725 {
5726   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5727   if(endPV < 0) return;
5728   if(appData.autoCopyPV) CopyFENToClipboard();
5729   endPV = -1;
5730   if(extendGame && currentMove > forwardMostMove) {
5731         Boolean saveAnimate = appData.animate;
5732         if(pushed) {
5733             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5734                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5735             } else storedGames--; // abandon shelved tail of original game
5736         }
5737         pushed = FALSE;
5738         forwardMostMove = currentMove;
5739         currentMove = oldFMM;
5740         appData.animate = FALSE;
5741         ToNrEvent(forwardMostMove);
5742         appData.animate = saveAnimate;
5743   }
5744   currentMove = forwardMostMove;
5745   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5746   ClearPremoveHighlights();
5747   DrawPosition(TRUE, boards[currentMove]);
5748 }
5749
5750 void
5751 MovePV (int x, int y, int h)
5752 { // step through PV based on mouse coordinates (called on mouse move)
5753   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5754
5755   // we must somehow check if right button is still down (might be released off board!)
5756   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5757   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5758   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5759   if(!step) return;
5760   lastX = x; lastY = y;
5761
5762   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5763   if(endPV < 0) return;
5764   if(y < margin) step = 1; else
5765   if(y > h - margin) step = -1;
5766   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5767   currentMove += step;
5768   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5769   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5770                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5771   DrawPosition(FALSE, boards[currentMove]);
5772 }
5773
5774
5775 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5776 // All positions will have equal probability, but the current method will not provide a unique
5777 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5778 #define DARK 1
5779 #define LITE 2
5780 #define ANY 3
5781
5782 int squaresLeft[4];
5783 int piecesLeft[(int)BlackPawn];
5784 int seed, nrOfShuffles;
5785
5786 void
5787 GetPositionNumber ()
5788 {       // sets global variable seed
5789         int i;
5790
5791         seed = appData.defaultFrcPosition;
5792         if(seed < 0) { // randomize based on time for negative FRC position numbers
5793                 for(i=0; i<50; i++) seed += random();
5794                 seed = random() ^ random() >> 8 ^ random() << 8;
5795                 if(seed<0) seed = -seed;
5796         }
5797 }
5798
5799 int
5800 put (Board board, int pieceType, int rank, int n, int shade)
5801 // put the piece on the (n-1)-th empty squares of the given shade
5802 {
5803         int i;
5804
5805         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5806                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5807                         board[rank][i] = (ChessSquare) pieceType;
5808                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5809                         squaresLeft[ANY]--;
5810                         piecesLeft[pieceType]--;
5811                         return i;
5812                 }
5813         }
5814         return -1;
5815 }
5816
5817
5818 void
5819 AddOnePiece (Board board, int pieceType, int rank, int shade)
5820 // calculate where the next piece goes, (any empty square), and put it there
5821 {
5822         int i;
5823
5824         i = seed % squaresLeft[shade];
5825         nrOfShuffles *= squaresLeft[shade];
5826         seed /= squaresLeft[shade];
5827         put(board, pieceType, rank, i, shade);
5828 }
5829
5830 void
5831 AddTwoPieces (Board board, int pieceType, int rank)
5832 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5833 {
5834         int i, n=squaresLeft[ANY], j=n-1, k;
5835
5836         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5837         i = seed % k;  // pick one
5838         nrOfShuffles *= k;
5839         seed /= k;
5840         while(i >= j) i -= j--;
5841         j = n - 1 - j; i += j;
5842         put(board, pieceType, rank, j, ANY);
5843         put(board, pieceType, rank, i, ANY);
5844 }
5845
5846 void
5847 SetUpShuffle (Board board, int number)
5848 {
5849         int i, p, first=1;
5850
5851         GetPositionNumber(); nrOfShuffles = 1;
5852
5853         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5854         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5855         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5856
5857         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5858
5859         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5860             p = (int) board[0][i];
5861             if(p < (int) BlackPawn) piecesLeft[p] ++;
5862             board[0][i] = EmptySquare;
5863         }
5864
5865         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5866             // shuffles restricted to allow normal castling put KRR first
5867             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5868                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5869             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5870                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5871             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5872                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5873             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5874                 put(board, WhiteRook, 0, 0, ANY);
5875             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5876         }
5877
5878         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5879             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5880             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5881                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5882                 while(piecesLeft[p] >= 2) {
5883                     AddOnePiece(board, p, 0, LITE);
5884                     AddOnePiece(board, p, 0, DARK);
5885                 }
5886                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5887             }
5888
5889         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5890             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5891             // but we leave King and Rooks for last, to possibly obey FRC restriction
5892             if(p == (int)WhiteRook) continue;
5893             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5894             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5895         }
5896
5897         // now everything is placed, except perhaps King (Unicorn) and Rooks
5898
5899         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5900             // Last King gets castling rights
5901             while(piecesLeft[(int)WhiteUnicorn]) {
5902                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906             while(piecesLeft[(int)WhiteKing]) {
5907                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5908                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5909             }
5910
5911
5912         } else {
5913             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5914             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5915         }
5916
5917         // Only Rooks can be left; simply place them all
5918         while(piecesLeft[(int)WhiteRook]) {
5919                 i = put(board, WhiteRook, 0, 0, ANY);
5920                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5921                         if(first) {
5922                                 first=0;
5923                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5924                         }
5925                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5926                 }
5927         }
5928         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5929             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5930         }
5931
5932         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5933 }
5934
5935 int
5936 SetCharTable (char *table, const char * map)
5937 /* [HGM] moved here from winboard.c because of its general usefulness */
5938 /*       Basically a safe strcpy that uses the last character as King */
5939 {
5940     int result = FALSE; int NrPieces;
5941
5942     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5943                     && NrPieces >= 12 && !(NrPieces&1)) {
5944         int i; /* [HGM] Accept even length from 12 to 34 */
5945
5946         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5947         for( i=0; i<NrPieces/2-1; i++ ) {
5948             table[i] = map[i];
5949             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5950         }
5951         table[(int) WhiteKing]  = map[NrPieces/2-1];
5952         table[(int) BlackKing]  = map[NrPieces-1];
5953
5954         result = TRUE;
5955     }
5956
5957     return result;
5958 }
5959
5960 void
5961 Prelude (Board board)
5962 {       // [HGM] superchess: random selection of exo-pieces
5963         int i, j, k; ChessSquare p;
5964         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5965
5966         GetPositionNumber(); // use FRC position number
5967
5968         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5969             SetCharTable(pieceToChar, appData.pieceToCharTable);
5970             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5971                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5972         }
5973
5974         j = seed%4;                 seed /= 4;
5975         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%3 + (seed%3 >= j); seed /= 3;
5979         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5980         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5981         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5982         j = seed%3;                 seed /= 3;
5983         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5984         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5985         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5986         j = seed%2 + (seed%2 >= j); seed /= 2;
5987         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5988         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5989         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5990         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5991         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5992         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5993         put(board, exoPieces[0],    0, 0, ANY);
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5995 }
5996
5997 void
5998 InitPosition (int redraw)
5999 {
6000     ChessSquare (* pieces)[BOARD_FILES];
6001     int i, j, pawnRow=1, pieceRows=1, overrule,
6002     oldx = gameInfo.boardWidth,
6003     oldy = gameInfo.boardHeight,
6004     oldh = gameInfo.holdingsWidth;
6005     static int oldv;
6006
6007     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6008
6009     /* [AS] Initialize pv info list [HGM] and game status */
6010     {
6011         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6012             pvInfoList[i].depth = 0;
6013             boards[i][EP_STATUS] = EP_NONE;
6014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6015         }
6016
6017         initialRulePlies = 0; /* 50-move counter start */
6018
6019         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6020         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6021     }
6022
6023
6024     /* [HGM] logic here is completely changed. In stead of full positions */
6025     /* the initialized data only consist of the two backranks. The switch */
6026     /* selects which one we will use, which is than copied to the Board   */
6027     /* initialPosition, which for the rest is initialized by Pawns and    */
6028     /* empty squares. This initial position is then copied to boards[0],  */
6029     /* possibly after shuffling, so that it remains available.            */
6030
6031     gameInfo.holdingsWidth = 0; /* default board sizes */
6032     gameInfo.boardWidth    = 8;
6033     gameInfo.boardHeight   = 8;
6034     gameInfo.holdingsSize  = 0;
6035     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6036     for(i=0; i<BOARD_FILES-6; i++)
6037       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6038     initialPosition[EP_STATUS] = EP_NONE;
6039     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6040     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6041     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6042          SetCharTable(pieceNickName, appData.pieceNickNames);
6043     else SetCharTable(pieceNickName, "............");
6044     pieces = FIDEArray;
6045
6046     switch (gameInfo.variant) {
6047     case VariantFischeRandom:
6048       shuffleOpenings = TRUE;
6049       appData.fischerCastling = TRUE;
6050     default:
6051       break;
6052     case VariantShatranj:
6053       pieces = ShatranjArray;
6054       nrCastlingRights = 0;
6055       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6056       break;
6057     case VariantMakruk:
6058       pieces = makrukArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6061       break;
6062     case VariantASEAN:
6063       pieces = aseanArray;
6064       nrCastlingRights = 0;
6065       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6066       break;
6067     case VariantTwoKings:
6068       pieces = twoKingsArray;
6069       break;
6070     case VariantGrand:
6071       pieces = GrandArray;
6072       nrCastlingRights = 0;
6073       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6074       gameInfo.boardWidth = 10;
6075       gameInfo.boardHeight = 10;
6076       gameInfo.holdingsSize = 7;
6077       break;
6078     case VariantCapaRandom:
6079       shuffleOpenings = TRUE;
6080       appData.fischerCastling = TRUE;
6081     case VariantCapablanca:
6082       pieces = CapablancaArray;
6083       gameInfo.boardWidth = 10;
6084       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6085       break;
6086     case VariantGothic:
6087       pieces = GothicArray;
6088       gameInfo.boardWidth = 10;
6089       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6090       break;
6091     case VariantSChess:
6092       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6093       gameInfo.holdingsSize = 7;
6094       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6095       break;
6096     case VariantJanus:
6097       pieces = JanusArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6100       nrCastlingRights = 6;
6101         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6103         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6104         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6105         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6106         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6107       break;
6108     case VariantFalcon:
6109       pieces = FalconArray;
6110       gameInfo.boardWidth = 10;
6111       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6112       break;
6113     case VariantXiangqi:
6114       pieces = XiangqiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 10;
6117       nrCastlingRights = 0;
6118       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6119       break;
6120     case VariantShogi:
6121       pieces = ShogiArray;
6122       gameInfo.boardWidth  = 9;
6123       gameInfo.boardHeight = 9;
6124       gameInfo.holdingsSize = 7;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6127       break;
6128     case VariantChu:
6129       pieces = ChuArray; pieceRows = 3;
6130       gameInfo.boardWidth  = 12;
6131       gameInfo.boardHeight = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6134                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6135       break;
6136     case VariantCourier:
6137       pieces = CourierArray;
6138       gameInfo.boardWidth  = 12;
6139       nrCastlingRights = 0;
6140       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6141       break;
6142     case VariantKnightmate:
6143       pieces = KnightmateArray;
6144       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6145       break;
6146     case VariantSpartan:
6147       pieces = SpartanArray;
6148       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6149       break;
6150     case VariantLion:
6151       pieces = lionArray;
6152       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6153       break;
6154     case VariantChuChess:
6155       pieces = ChuChessArray;
6156       gameInfo.boardWidth = 10;
6157       gameInfo.boardHeight = 10;
6158       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6159       break;
6160     case VariantFairy:
6161       pieces = fairyArray;
6162       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6163       break;
6164     case VariantGreat:
6165       pieces = GreatArray;
6166       gameInfo.boardWidth = 10;
6167       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6168       gameInfo.holdingsSize = 8;
6169       break;
6170     case VariantSuper:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6173       gameInfo.holdingsSize = 8;
6174       startedFromSetupPosition = TRUE;
6175       break;
6176     case VariantCrazyhouse:
6177     case VariantBughouse:
6178       pieces = FIDEArray;
6179       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6180       gameInfo.holdingsSize = 5;
6181       break;
6182     case VariantWildCastle:
6183       pieces = FIDEArray;
6184       /* !!?shuffle with kings guaranteed to be on d or e file */
6185       shuffleOpenings = 1;
6186       break;
6187     case VariantNoCastle:
6188       pieces = FIDEArray;
6189       nrCastlingRights = 0;
6190       /* !!?unconstrained back-rank shuffle */
6191       shuffleOpenings = 1;
6192       break;
6193     }
6194
6195     overrule = 0;
6196     if(appData.NrFiles >= 0) {
6197         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6198         gameInfo.boardWidth = appData.NrFiles;
6199     }
6200     if(appData.NrRanks >= 0) {
6201         gameInfo.boardHeight = appData.NrRanks;
6202     }
6203     if(appData.holdingsSize >= 0) {
6204         i = appData.holdingsSize;
6205         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6206         gameInfo.holdingsSize = i;
6207     }
6208     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6209     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6210         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6211
6212     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6213     if(pawnRow < 1) pawnRow = 1;
6214     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6215        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6216     if(gameInfo.variant == VariantChu) pawnRow = 3;
6217
6218     /* User pieceToChar list overrules defaults */
6219     if(appData.pieceToCharTable != NULL)
6220         SetCharTable(pieceToChar, appData.pieceToCharTable);
6221
6222     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6223
6224         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6225             s = (ChessSquare) 0; /* account holding counts in guard band */
6226         for( i=0; i<BOARD_HEIGHT; i++ )
6227             initialPosition[i][j] = s;
6228
6229         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6230         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6231         initialPosition[pawnRow][j] = WhitePawn;
6232         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6233         if(gameInfo.variant == VariantXiangqi) {
6234             if(j&1) {
6235                 initialPosition[pawnRow][j] =
6236                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6237                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6238                    initialPosition[2][j] = WhiteCannon;
6239                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6240                 }
6241             }
6242         }
6243         if(gameInfo.variant == VariantChu) {
6244              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6245                initialPosition[pawnRow+1][j] = WhiteCobra,
6246                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6247              for(i=1; i<pieceRows; i++) {
6248                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6249                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6250              }
6251         }
6252         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6253             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6254                initialPosition[0][j] = WhiteRook;
6255                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6256             }
6257         }
6258         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6259     }
6260     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6261     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6262
6263             j=BOARD_LEFT+1;
6264             initialPosition[1][j] = WhiteBishop;
6265             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6266             j=BOARD_RGHT-2;
6267             initialPosition[1][j] = WhiteRook;
6268             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6269     }
6270
6271     if( nrCastlingRights == -1) {
6272         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6273         /*       This sets default castling rights from none to normal corners   */
6274         /* Variants with other castling rights must set them themselves above    */
6275         nrCastlingRights = 6;
6276
6277         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6278         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6279         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6280         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6281         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6282         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6283      }
6284
6285      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6286      if(gameInfo.variant == VariantGreat) { // promotion commoners
6287         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6288         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6289         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6290         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6291      }
6292      if( gameInfo.variant == VariantSChess ) {
6293       initialPosition[1][0] = BlackMarshall;
6294       initialPosition[2][0] = BlackAngel;
6295       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6296       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6297       initialPosition[1][1] = initialPosition[2][1] =
6298       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6299      }
6300   if (appData.debugMode) {
6301     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6302   }
6303     if(shuffleOpenings) {
6304         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6305         startedFromSetupPosition = TRUE;
6306     }
6307     if(startedFromPositionFile) {
6308       /* [HGM] loadPos: use PositionFile for every new game */
6309       CopyBoard(initialPosition, filePosition);
6310       for(i=0; i<nrCastlingRights; i++)
6311           initialRights[i] = filePosition[CASTLING][i];
6312       startedFromSetupPosition = TRUE;
6313     }
6314
6315     CopyBoard(boards[0], initialPosition);
6316
6317     if(oldx != gameInfo.boardWidth ||
6318        oldy != gameInfo.boardHeight ||
6319        oldv != gameInfo.variant ||
6320        oldh != gameInfo.holdingsWidth
6321                                          )
6322             InitDrawingSizes(-2 ,0);
6323
6324     oldv = gameInfo.variant;
6325     if (redraw)
6326       DrawPosition(TRUE, boards[currentMove]);
6327 }
6328
6329 void
6330 SendBoard (ChessProgramState *cps, int moveNum)
6331 {
6332     char message[MSG_SIZ];
6333
6334     if (cps->useSetboard) {
6335       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6336       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6337       SendToProgram(message, cps);
6338       free(fen);
6339
6340     } else {
6341       ChessSquare *bp;
6342       int i, j, left=0, right=BOARD_WIDTH;
6343       /* Kludge to set black to move, avoiding the troublesome and now
6344        * deprecated "black" command.
6345        */
6346       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6347         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6348
6349       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6350
6351       SendToProgram("edit\n", cps);
6352       SendToProgram("#\n", cps);
6353       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6354         bp = &boards[moveNum][i][left];
6355         for (j = left; j < right; j++, bp++) {
6356           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6357           if ((int) *bp < (int) BlackPawn) {
6358             if(j == BOARD_RGHT+1)
6359                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6360             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6361             if(message[0] == '+' || message[0] == '~') {
6362               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6363                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6364                         AAA + j, ONE + i);
6365             }
6366             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6367                 message[1] = BOARD_RGHT   - 1 - j + '1';
6368                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6369             }
6370             SendToProgram(message, cps);
6371           }
6372         }
6373       }
6374
6375       SendToProgram("c\n", cps);
6376       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6377         bp = &boards[moveNum][i][left];
6378         for (j = left; j < right; j++, bp++) {
6379           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6380           if (((int) *bp != (int) EmptySquare)
6381               && ((int) *bp >= (int) BlackPawn)) {
6382             if(j == BOARD_LEFT-2)
6383                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6384             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6385                     AAA + j, ONE + i);
6386             if(message[0] == '+' || message[0] == '~') {
6387               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6388                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6389                         AAA + j, ONE + i);
6390             }
6391             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6392                 message[1] = BOARD_RGHT   - 1 - j + '1';
6393                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6394             }
6395             SendToProgram(message, cps);
6396           }
6397         }
6398       }
6399
6400       SendToProgram(".\n", cps);
6401     }
6402     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6403 }
6404
6405 char exclusionHeader[MSG_SIZ];
6406 int exCnt, excludePtr;
6407 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6408 static Exclusion excluTab[200];
6409 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6410
6411 static void
6412 WriteMap (int s)
6413 {
6414     int j;
6415     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6416     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6417 }
6418
6419 static void
6420 ClearMap ()
6421 {
6422     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6423     excludePtr = 24; exCnt = 0;
6424     WriteMap(0);
6425 }
6426
6427 static void
6428 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6429 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6430     char buf[2*MOVE_LEN], *p;
6431     Exclusion *e = excluTab;
6432     int i;
6433     for(i=0; i<exCnt; i++)
6434         if(e[i].ff == fromX && e[i].fr == fromY &&
6435            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6436     if(i == exCnt) { // was not in exclude list; add it
6437         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6438         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6439             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6440             return; // abort
6441         }
6442         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6443         excludePtr++; e[i].mark = excludePtr++;
6444         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6445         exCnt++;
6446     }
6447     exclusionHeader[e[i].mark] = state;
6448 }
6449
6450 static int
6451 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6452 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6453     char buf[MSG_SIZ];
6454     int j, k;
6455     ChessMove moveType;
6456     if((signed char)promoChar == -1) { // kludge to indicate best move
6457         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6458             return 1; // if unparsable, abort
6459     }
6460     // update exclusion map (resolving toggle by consulting existing state)
6461     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6462     j = k%8; k >>= 3;
6463     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6464     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6465          excludeMap[k] |=   1<<j;
6466     else excludeMap[k] &= ~(1<<j);
6467     // update header
6468     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6469     // inform engine
6470     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6471     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6472     SendToBoth(buf);
6473     return (state == '+');
6474 }
6475
6476 static void
6477 ExcludeClick (int index)
6478 {
6479     int i, j;
6480     Exclusion *e = excluTab;
6481     if(index < 25) { // none, best or tail clicked
6482         if(index < 13) { // none: include all
6483             WriteMap(0); // clear map
6484             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6485             SendToBoth("include all\n"); // and inform engine
6486         } else if(index > 18) { // tail
6487             if(exclusionHeader[19] == '-') { // tail was excluded
6488                 SendToBoth("include all\n");
6489                 WriteMap(0); // clear map completely
6490                 // now re-exclude selected moves
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6493             } else { // tail was included or in mixed state
6494                 SendToBoth("exclude all\n");
6495                 WriteMap(0xFF); // fill map completely
6496                 // now re-include selected moves
6497                 j = 0; // count them
6498                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6499                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6500                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6501             }
6502         } else { // best
6503             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6504         }
6505     } else {
6506         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6507             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6508             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6509             break;
6510         }
6511     }
6512 }
6513
6514 ChessSquare
6515 DefaultPromoChoice (int white)
6516 {
6517     ChessSquare result;
6518     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6519        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6520         result = WhiteFerz; // no choice
6521     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6522         result= WhiteKing; // in Suicide Q is the last thing we want
6523     else if(gameInfo.variant == VariantSpartan)
6524         result = white ? WhiteQueen : WhiteAngel;
6525     else result = WhiteQueen;
6526     if(!white) result = WHITE_TO_BLACK result;
6527     return result;
6528 }
6529
6530 static int autoQueen; // [HGM] oneclick
6531
6532 int
6533 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6534 {
6535     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6536     /* [HGM] add Shogi promotions */
6537     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6538     ChessSquare piece, partner;
6539     ChessMove moveType;
6540     Boolean premove;
6541
6542     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6543     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6544
6545     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6546       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6547         return FALSE;
6548
6549     piece = boards[currentMove][fromY][fromX];
6550     if(gameInfo.variant == VariantChu) {
6551         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6552         promotionZoneSize = BOARD_HEIGHT/3;
6553         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6554     } else if(gameInfo.variant == VariantShogi) {
6555         promotionZoneSize = BOARD_HEIGHT/3;
6556         highestPromotingPiece = (int)WhiteAlfil;
6557     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6558         promotionZoneSize = 3;
6559     }
6560
6561     // Treat Lance as Pawn when it is not representing Amazon or Lance
6562     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6563         if(piece == WhiteLance) piece = WhitePawn; else
6564         if(piece == BlackLance) piece = BlackPawn;
6565     }
6566
6567     // next weed out all moves that do not touch the promotion zone at all
6568     if((int)piece >= BlackPawn) {
6569         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6570              return FALSE;
6571         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6572         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6573     } else {
6574         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6575            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6576         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6577              return FALSE;
6578     }
6579
6580     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6581
6582     // weed out mandatory Shogi promotions
6583     if(gameInfo.variant == VariantShogi) {
6584         if(piece >= BlackPawn) {
6585             if(toY == 0 && piece == BlackPawn ||
6586                toY == 0 && piece == BlackQueen ||
6587                toY <= 1 && piece == BlackKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         } else {
6592             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6593                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6594                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6595                 *promoChoice = '+';
6596                 return FALSE;
6597             }
6598         }
6599     }
6600
6601     // weed out obviously illegal Pawn moves
6602     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6603         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6604         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6605         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6606         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6607         // note we are not allowed to test for valid (non-)capture, due to premove
6608     }
6609
6610     // we either have a choice what to promote to, or (in Shogi) whether to promote
6611     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6612        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6613         ChessSquare p=BlackFerz;  // no choice
6614         while(p < EmptySquare) {  //but make sure we use piece that exists
6615             *promoChoice = PieceToChar(p++);
6616             if(*promoChoice != '.') break;
6617         }
6618         return FALSE;
6619     }
6620     // no sense asking what we must promote to if it is going to explode...
6621     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6622         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6623         return FALSE;
6624     }
6625     // give caller the default choice even if we will not make it
6626     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6627     partner = piece; // pieces can promote if the pieceToCharTable says so
6628     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6629     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6630     if(        sweepSelect && gameInfo.variant != VariantGreat
6631                            && gameInfo.variant != VariantGrand
6632                            && gameInfo.variant != VariantSuper) return FALSE;
6633     if(autoQueen) return FALSE; // predetermined
6634
6635     // suppress promotion popup on illegal moves that are not premoves
6636     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6637               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6638     if(appData.testLegality && !premove) {
6639         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6640                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6641         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6642         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6643             return FALSE;
6644     }
6645
6646     return TRUE;
6647 }
6648
6649 int
6650 InPalace (int row, int column)
6651 {   /* [HGM] for Xiangqi */
6652     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6653          column < (BOARD_WIDTH + 4)/2 &&
6654          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6655     return FALSE;
6656 }
6657
6658 int
6659 PieceForSquare (int x, int y)
6660 {
6661   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6662      return -1;
6663   else
6664      return boards[currentMove][y][x];
6665 }
6666
6667 int
6668 OKToStartUserMove (int x, int y)
6669 {
6670     ChessSquare from_piece;
6671     int white_piece;
6672
6673     if (matchMode) return FALSE;
6674     if (gameMode == EditPosition) return TRUE;
6675
6676     if (x >= 0 && y >= 0)
6677       from_piece = boards[currentMove][y][x];
6678     else
6679       from_piece = EmptySquare;
6680
6681     if (from_piece == EmptySquare) return FALSE;
6682
6683     white_piece = (int)from_piece >= (int)WhitePawn &&
6684       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6685
6686     switch (gameMode) {
6687       case AnalyzeFile:
6688       case TwoMachinesPlay:
6689       case EndOfGame:
6690         return FALSE;
6691
6692       case IcsObserving:
6693       case IcsIdle:
6694         return FALSE;
6695
6696       case MachinePlaysWhite:
6697       case IcsPlayingBlack:
6698         if (appData.zippyPlay) return FALSE;
6699         if (white_piece) {
6700             DisplayMoveError(_("You are playing Black"));
6701             return FALSE;
6702         }
6703         break;
6704
6705       case MachinePlaysBlack:
6706       case IcsPlayingWhite:
6707         if (appData.zippyPlay) return FALSE;
6708         if (!white_piece) {
6709             DisplayMoveError(_("You are playing White"));
6710             return FALSE;
6711         }
6712         break;
6713
6714       case PlayFromGameFile:
6715             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6716       case EditGame:
6717         if (!white_piece && WhiteOnMove(currentMove)) {
6718             DisplayMoveError(_("It is White's turn"));
6719             return FALSE;
6720         }
6721         if (white_piece && !WhiteOnMove(currentMove)) {
6722             DisplayMoveError(_("It is Black's turn"));
6723             return FALSE;
6724         }
6725         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6726             /* Editing correspondence game history */
6727             /* Could disallow this or prompt for confirmation */
6728             cmailOldMove = -1;
6729         }
6730         break;
6731
6732       case BeginningOfGame:
6733         if (appData.icsActive) return FALSE;
6734         if (!appData.noChessProgram) {
6735             if (!white_piece) {
6736                 DisplayMoveError(_("You are playing White"));
6737                 return FALSE;
6738             }
6739         }
6740         break;
6741
6742       case Training:
6743         if (!white_piece && WhiteOnMove(currentMove)) {
6744             DisplayMoveError(_("It is White's turn"));
6745             return FALSE;
6746         }
6747         if (white_piece && !WhiteOnMove(currentMove)) {
6748             DisplayMoveError(_("It is Black's turn"));
6749             return FALSE;
6750         }
6751         break;
6752
6753       default:
6754       case IcsExamining:
6755         break;
6756     }
6757     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6758         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6759         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6760         && gameMode != AnalyzeFile && gameMode != Training) {
6761         DisplayMoveError(_("Displayed position is not current"));
6762         return FALSE;
6763     }
6764     return TRUE;
6765 }
6766
6767 Boolean
6768 OnlyMove (int *x, int *y, Boolean captures)
6769 {
6770     DisambiguateClosure cl;
6771     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6772     switch(gameMode) {
6773       case MachinePlaysBlack:
6774       case IcsPlayingWhite:
6775       case BeginningOfGame:
6776         if(!WhiteOnMove(currentMove)) return FALSE;
6777         break;
6778       case MachinePlaysWhite:
6779       case IcsPlayingBlack:
6780         if(WhiteOnMove(currentMove)) return FALSE;
6781         break;
6782       case EditGame:
6783         break;
6784       default:
6785         return FALSE;
6786     }
6787     cl.pieceIn = EmptySquare;
6788     cl.rfIn = *y;
6789     cl.ffIn = *x;
6790     cl.rtIn = -1;
6791     cl.ftIn = -1;
6792     cl.promoCharIn = NULLCHAR;
6793     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6794     if( cl.kind == NormalMove ||
6795         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6796         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6797         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6798       fromX = cl.ff;
6799       fromY = cl.rf;
6800       *x = cl.ft;
6801       *y = cl.rt;
6802       return TRUE;
6803     }
6804     if(cl.kind != ImpossibleMove) return FALSE;
6805     cl.pieceIn = EmptySquare;
6806     cl.rfIn = -1;
6807     cl.ffIn = -1;
6808     cl.rtIn = *y;
6809     cl.ftIn = *x;
6810     cl.promoCharIn = NULLCHAR;
6811     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6812     if( cl.kind == NormalMove ||
6813         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6814         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6815         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6816       fromX = cl.ff;
6817       fromY = cl.rf;
6818       *x = cl.ft;
6819       *y = cl.rt;
6820       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6821       return TRUE;
6822     }
6823     return FALSE;
6824 }
6825
6826 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6827 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6828 int lastLoadGameUseList = FALSE;
6829 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6830 ChessMove lastLoadGameStart = EndOfFile;
6831 int doubleClick;
6832 Boolean addToBookFlag;
6833
6834 void
6835 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6836 {
6837     ChessMove moveType;
6838     ChessSquare pup;
6839     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6840
6841     /* Check if the user is playing in turn.  This is complicated because we
6842        let the user "pick up" a piece before it is his turn.  So the piece he
6843        tried to pick up may have been captured by the time he puts it down!
6844        Therefore we use the color the user is supposed to be playing in this
6845        test, not the color of the piece that is currently on the starting
6846        square---except in EditGame mode, where the user is playing both
6847        sides; fortunately there the capture race can't happen.  (It can
6848        now happen in IcsExamining mode, but that's just too bad.  The user
6849        will get a somewhat confusing message in that case.)
6850        */
6851
6852     switch (gameMode) {
6853       case AnalyzeFile:
6854       case TwoMachinesPlay:
6855       case EndOfGame:
6856       case IcsObserving:
6857       case IcsIdle:
6858         /* We switched into a game mode where moves are not accepted,
6859            perhaps while the mouse button was down. */
6860         return;
6861
6862       case MachinePlaysWhite:
6863         /* User is moving for Black */
6864         if (WhiteOnMove(currentMove)) {
6865             DisplayMoveError(_("It is White's turn"));
6866             return;
6867         }
6868         break;
6869
6870       case MachinePlaysBlack:
6871         /* User is moving for White */
6872         if (!WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is Black's turn"));
6874             return;
6875         }
6876         break;
6877
6878       case PlayFromGameFile:
6879             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6880       case EditGame:
6881       case IcsExamining:
6882       case BeginningOfGame:
6883       case AnalyzeMode:
6884       case Training:
6885         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6886         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6887             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6888             /* User is moving for Black */
6889             if (WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is White's turn"));
6891                 return;
6892             }
6893         } else {
6894             /* User is moving for White */
6895             if (!WhiteOnMove(currentMove)) {
6896                 DisplayMoveError(_("It is Black's turn"));
6897                 return;
6898             }
6899         }
6900         break;
6901
6902       case IcsPlayingBlack:
6903         /* User is moving for Black */
6904         if (WhiteOnMove(currentMove)) {
6905             if (!appData.premove) {
6906                 DisplayMoveError(_("It is White's turn"));
6907             } else if (toX >= 0 && toY >= 0) {
6908                 premoveToX = toX;
6909                 premoveToY = toY;
6910                 premoveFromX = fromX;
6911                 premoveFromY = fromY;
6912                 premovePromoChar = promoChar;
6913                 gotPremove = 1;
6914                 if (appData.debugMode)
6915                     fprintf(debugFP, "Got premove: fromX %d,"
6916                             "fromY %d, toX %d, toY %d\n",
6917                             fromX, fromY, toX, toY);
6918             }
6919             return;
6920         }
6921         break;
6922
6923       case IcsPlayingWhite:
6924         /* User is moving for White */
6925         if (!WhiteOnMove(currentMove)) {
6926             if (!appData.premove) {
6927                 DisplayMoveError(_("It is Black's turn"));
6928             } else if (toX >= 0 && toY >= 0) {
6929                 premoveToX = toX;
6930                 premoveToY = toY;
6931                 premoveFromX = fromX;
6932                 premoveFromY = fromY;
6933                 premovePromoChar = promoChar;
6934                 gotPremove = 1;
6935                 if (appData.debugMode)
6936                     fprintf(debugFP, "Got premove: fromX %d,"
6937                             "fromY %d, toX %d, toY %d\n",
6938                             fromX, fromY, toX, toY);
6939             }
6940             return;
6941         }
6942         break;
6943
6944       default:
6945         break;
6946
6947       case EditPosition:
6948         /* EditPosition, empty square, or different color piece;
6949            click-click move is possible */
6950         if (toX == -2 || toY == -2) {
6951             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6952             DrawPosition(FALSE, boards[currentMove]);
6953             return;
6954         } else if (toX >= 0 && toY >= 0) {
6955             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6956                 ChessSquare q, p = boards[0][rf][ff];
6957                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6958                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6959                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6960                 if(PieceToChar(q) == '+') gatingPiece = p;
6961             }
6962             boards[0][toY][toX] = boards[0][fromY][fromX];
6963             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6964                 if(boards[0][fromY][0] != EmptySquare) {
6965                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6966                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6967                 }
6968             } else
6969             if(fromX == BOARD_RGHT+1) {
6970                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6971                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6972                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6973                 }
6974             } else
6975             boards[0][fromY][fromX] = gatingPiece;
6976             DrawPosition(FALSE, boards[currentMove]);
6977             return;
6978         }
6979         return;
6980     }
6981
6982     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6983     pup = boards[currentMove][toY][toX];
6984
6985     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6986     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6987          if( pup != EmptySquare ) return;
6988          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6989            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6990                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6991            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6992            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6993            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6994            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6995          fromY = DROP_RANK;
6996     }
6997
6998     /* [HGM] always test for legality, to get promotion info */
6999     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7000                                          fromY, fromX, toY, toX, promoChar);
7001
7002     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7003
7004     /* [HGM] but possibly ignore an IllegalMove result */
7005     if (appData.testLegality) {
7006         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7007             DisplayMoveError(_("Illegal move"));
7008             return;
7009         }
7010     }
7011
7012     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7013         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7014              ClearPremoveHighlights(); // was included
7015         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7016         return;
7017     }
7018
7019     if(addToBookFlag) { // adding moves to book
7020         char buf[MSG_SIZ], move[MSG_SIZ];
7021         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7022         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7023         AddBookMove(buf);
7024         addToBookFlag = FALSE;
7025         ClearHighlights();
7026         return;
7027     }
7028
7029     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7030 }
7031
7032 /* Common tail of UserMoveEvent and DropMenuEvent */
7033 int
7034 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7035 {
7036     char *bookHit = 0;
7037
7038     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7039         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7040         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7041         if(WhiteOnMove(currentMove)) {
7042             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7043         } else {
7044             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7045         }
7046     }
7047
7048     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7049        move type in caller when we know the move is a legal promotion */
7050     if(moveType == NormalMove && promoChar)
7051         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7052
7053     /* [HGM] <popupFix> The following if has been moved here from
7054        UserMoveEvent(). Because it seemed to belong here (why not allow
7055        piece drops in training games?), and because it can only be
7056        performed after it is known to what we promote. */
7057     if (gameMode == Training) {
7058       /* compare the move played on the board to the next move in the
7059        * game. If they match, display the move and the opponent's response.
7060        * If they don't match, display an error message.
7061        */
7062       int saveAnimate;
7063       Board testBoard;
7064       CopyBoard(testBoard, boards[currentMove]);
7065       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7066
7067       if (CompareBoards(testBoard, boards[currentMove+1])) {
7068         ForwardInner(currentMove+1);
7069
7070         /* Autoplay the opponent's response.
7071          * if appData.animate was TRUE when Training mode was entered,
7072          * the response will be animated.
7073          */
7074         saveAnimate = appData.animate;
7075         appData.animate = animateTraining;
7076         ForwardInner(currentMove+1);
7077         appData.animate = saveAnimate;
7078
7079         /* check for the end of the game */
7080         if (currentMove >= forwardMostMove) {
7081           gameMode = PlayFromGameFile;
7082           ModeHighlight();
7083           SetTrainingModeOff();
7084           DisplayInformation(_("End of game"));
7085         }
7086       } else {
7087         DisplayError(_("Incorrect move"), 0);
7088       }
7089       return 1;
7090     }
7091
7092   /* Ok, now we know that the move is good, so we can kill
7093      the previous line in Analysis Mode */
7094   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7095                                 && currentMove < forwardMostMove) {
7096     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7097     else forwardMostMove = currentMove;
7098   }
7099
7100   ClearMap();
7101
7102   /* If we need the chess program but it's dead, restart it */
7103   ResurrectChessProgram();
7104
7105   /* A user move restarts a paused game*/
7106   if (pausing)
7107     PauseEvent();
7108
7109   thinkOutput[0] = NULLCHAR;
7110
7111   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7112
7113   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7114     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7115     return 1;
7116   }
7117
7118   if (gameMode == BeginningOfGame) {
7119     if (appData.noChessProgram) {
7120       gameMode = EditGame;
7121       SetGameInfo();
7122     } else {
7123       char buf[MSG_SIZ];
7124       gameMode = MachinePlaysBlack;
7125       StartClocks();
7126       SetGameInfo();
7127       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7128       DisplayTitle(buf);
7129       if (first.sendName) {
7130         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7131         SendToProgram(buf, &first);
7132       }
7133       StartClocks();
7134     }
7135     ModeHighlight();
7136   }
7137
7138   /* Relay move to ICS or chess engine */
7139   if (appData.icsActive) {
7140     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7141         gameMode == IcsExamining) {
7142       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7143         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7144         SendToICS("draw ");
7145         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7146       }
7147       // also send plain move, in case ICS does not understand atomic claims
7148       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7149       ics_user_moved = 1;
7150     }
7151   } else {
7152     if (first.sendTime && (gameMode == BeginningOfGame ||
7153                            gameMode == MachinePlaysWhite ||
7154                            gameMode == MachinePlaysBlack)) {
7155       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7156     }
7157     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7158          // [HGM] book: if program might be playing, let it use book
7159         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7160         first.maybeThinking = TRUE;
7161     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7162         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7163         SendBoard(&first, currentMove+1);
7164         if(second.analyzing) {
7165             if(!second.useSetboard) SendToProgram("undo\n", &second);
7166             SendBoard(&second, currentMove+1);
7167         }
7168     } else {
7169         SendMoveToProgram(forwardMostMove-1, &first);
7170         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7171     }
7172     if (currentMove == cmailOldMove + 1) {
7173       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7174     }
7175   }
7176
7177   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7178
7179   switch (gameMode) {
7180   case EditGame:
7181     if(appData.testLegality)
7182     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7183     case MT_NONE:
7184     case MT_CHECK:
7185       break;
7186     case MT_CHECKMATE:
7187     case MT_STAINMATE:
7188       if (WhiteOnMove(currentMove)) {
7189         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7190       } else {
7191         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7192       }
7193       break;
7194     case MT_STALEMATE:
7195       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7196       break;
7197     }
7198     break;
7199
7200   case MachinePlaysBlack:
7201   case MachinePlaysWhite:
7202     /* disable certain menu options while machine is thinking */
7203     SetMachineThinkingEnables();
7204     break;
7205
7206   default:
7207     break;
7208   }
7209
7210   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7211   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7212
7213   if(bookHit) { // [HGM] book: simulate book reply
7214         static char bookMove[MSG_SIZ]; // a bit generous?
7215
7216         programStats.nodes = programStats.depth = programStats.time =
7217         programStats.score = programStats.got_only_move = 0;
7218         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7219
7220         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7221         strcat(bookMove, bookHit);
7222         HandleMachineMove(bookMove, &first);
7223   }
7224   return 1;
7225 }
7226
7227 void
7228 MarkByFEN(char *fen)
7229 {
7230         int r, f;
7231         if(!appData.markers || !appData.highlightDragging) return;
7232         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7233         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7234         while(*fen) {
7235             int s = 0;
7236             marker[r][f] = 0;
7237             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7238             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7239             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7240             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7241             if(*fen == 'T') marker[r][f++] = 0; else
7242             if(*fen == 'Y') marker[r][f++] = 1; else
7243             if(*fen == 'G') marker[r][f++] = 3; else
7244             if(*fen == 'B') marker[r][f++] = 4; else
7245             if(*fen == 'C') marker[r][f++] = 5; else
7246             if(*fen == 'M') marker[r][f++] = 6; else
7247             if(*fen == 'W') marker[r][f++] = 7; else
7248             if(*fen == 'D') marker[r][f++] = 8; else
7249             if(*fen == 'R') marker[r][f++] = 2; else {
7250                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7251               f += s; fen -= s>0;
7252             }
7253             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7254             if(r < 0) break;
7255             fen++;
7256         }
7257         DrawPosition(TRUE, NULL);
7258 }
7259
7260 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7261
7262 void
7263 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7264 {
7265     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7266     Markers *m = (Markers *) closure;
7267     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7268         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7269                          || kind == WhiteCapturesEnPassant
7270                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7271     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7272 }
7273
7274 static int hoverSavedValid;
7275
7276 void
7277 MarkTargetSquares (int clear)
7278 {
7279   int x, y, sum=0;
7280   if(clear) { // no reason to ever suppress clearing
7281     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7282     hoverSavedValid = 0;
7283     if(!sum) return; // nothing was cleared,no redraw needed
7284   } else {
7285     int capt = 0;
7286     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7287        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7288     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7289     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7290       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7291       if(capt)
7292       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7293     }
7294   }
7295   DrawPosition(FALSE, NULL);
7296 }
7297
7298 int
7299 Explode (Board board, int fromX, int fromY, int toX, int toY)
7300 {
7301     if(gameInfo.variant == VariantAtomic &&
7302        (board[toY][toX] != EmptySquare ||                     // capture?
7303         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7304                          board[fromY][fromX] == BlackPawn   )
7305       )) {
7306         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7307         return TRUE;
7308     }
7309     return FALSE;
7310 }
7311
7312 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7313
7314 int
7315 CanPromote (ChessSquare piece, int y)
7316 {
7317         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7318         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7319         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7320         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7321            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7322            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7323          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7324         return (piece == BlackPawn && y <= zone ||
7325                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7326                 piece == BlackLance && y <= zone ||
7327                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7328 }
7329
7330 void
7331 HoverEvent (int xPix, int yPix, int x, int y)
7332 {
7333         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7334         int r, f;
7335         if(!first.highlight) return;
7336         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7337         if(x == oldX && y == oldY) return; // only do something if we enter new square
7338         oldFromX = fromX; oldFromY = fromY;
7339         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7342           hoverSavedValid = 1;
7343         } else if(oldX != x || oldY != y) {
7344           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7345           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7346           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7347             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7348           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7349             char buf[MSG_SIZ];
7350             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7351             SendToProgram(buf, &first);
7352           }
7353           oldX = x; oldY = y;
7354 //        SetHighlights(fromX, fromY, x, y);
7355         }
7356 }
7357
7358 void ReportClick(char *action, int x, int y)
7359 {
7360         char buf[MSG_SIZ]; // Inform engine of what user does
7361         int r, f;
7362         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7363           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7364             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7365         if(!first.highlight || gameMode == EditPosition) return;
7366         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7367         SendToProgram(buf, &first);
7368 }
7369
7370 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7371
7372 void
7373 LeftClick (ClickType clickType, int xPix, int yPix)
7374 {
7375     int x, y;
7376     Boolean saveAnimate;
7377     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7378     char promoChoice = NULLCHAR;
7379     ChessSquare piece;
7380     static TimeMark lastClickTime, prevClickTime;
7381
7382     x = EventToSquare(xPix, BOARD_WIDTH);
7383     y = EventToSquare(yPix, BOARD_HEIGHT);
7384     if (!flipView && y >= 0) {
7385         y = BOARD_HEIGHT - 1 - y;
7386     }
7387     if (flipView && x >= 0) {
7388         x = BOARD_WIDTH - 1 - x;
7389     }
7390
7391     if(appData.monoMouse && gameMode == EditPosition && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7392         static int dummy;
7393         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7394         right = TRUE;
7395         return;
7396     }
7397
7398     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7399
7400     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7401
7402     if (clickType == Press) ErrorPopDown();
7403     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7404
7405     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7406         defaultPromoChoice = promoSweep;
7407         promoSweep = EmptySquare;   // terminate sweep
7408         promoDefaultAltered = TRUE;
7409         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7410     }
7411
7412     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7413         if(clickType == Release) return; // ignore upclick of click-click destination
7414         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7415         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7416         if(gameInfo.holdingsWidth &&
7417                 (WhiteOnMove(currentMove)
7418                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7419                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7420             // click in right holdings, for determining promotion piece
7421             ChessSquare p = boards[currentMove][y][x];
7422             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7423             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7424             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7425                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7426                 fromX = fromY = -1;
7427                 return;
7428             }
7429         }
7430         DrawPosition(FALSE, boards[currentMove]);
7431         return;
7432     }
7433
7434     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7435     if(clickType == Press
7436             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7437               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7438               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7439         return;
7440
7441     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7442         // could be static click on premove from-square: abort premove
7443         gotPremove = 0;
7444         ClearPremoveHighlights();
7445     }
7446
7447     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7448         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7449
7450     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7451         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7452                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7453         defaultPromoChoice = DefaultPromoChoice(side);
7454     }
7455
7456     autoQueen = appData.alwaysPromoteToQueen;
7457
7458     if (fromX == -1) {
7459       int originalY = y;
7460       gatingPiece = EmptySquare;
7461       if (clickType != Press) {
7462         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7463             DragPieceEnd(xPix, yPix); dragging = 0;
7464             DrawPosition(FALSE, NULL);
7465         }
7466         return;
7467       }
7468       doubleClick = FALSE;
7469       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7470         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7471       }
7472       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7473       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7474          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7475          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7476             /* First square */
7477             if (OKToStartUserMove(fromX, fromY)) {
7478                 second = 0;
7479                 ReportClick("lift", x, y);
7480                 MarkTargetSquares(0);
7481                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7482                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7483                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7484                     promoSweep = defaultPromoChoice;
7485                     selectFlag = 0; lastX = xPix; lastY = yPix;
7486                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7487                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7488                 }
7489                 if (appData.highlightDragging) {
7490                     SetHighlights(fromX, fromY, -1, -1);
7491                 } else {
7492                     ClearHighlights();
7493                 }
7494             } else fromX = fromY = -1;
7495             return;
7496         }
7497     }
7498
7499     /* fromX != -1 */
7500     if (clickType == Press && gameMode != EditPosition) {
7501         ChessSquare fromP;
7502         ChessSquare toP;
7503         int frc;
7504
7505         // ignore off-board to clicks
7506         if(y < 0 || x < 0) return;
7507
7508         /* Check if clicking again on the same color piece */
7509         fromP = boards[currentMove][fromY][fromX];
7510         toP = boards[currentMove][y][x];
7511         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7512         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7513             legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7514            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7515              WhitePawn <= toP && toP <= WhiteKing &&
7516              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7517              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7518             (BlackPawn <= fromP && fromP <= BlackKing &&
7519              BlackPawn <= toP && toP <= BlackKing &&
7520              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7521              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7522             /* Clicked again on same color piece -- changed his mind */
7523             second = (x == fromX && y == fromY);
7524             killX = killY = -1;
7525             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7526                 second = FALSE; // first double-click rather than scond click
7527                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7528             }
7529             promoDefaultAltered = FALSE;
7530             MarkTargetSquares(1);
7531            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7532             if (appData.highlightDragging) {
7533                 SetHighlights(x, y, -1, -1);
7534             } else {
7535                 ClearHighlights();
7536             }
7537             if (OKToStartUserMove(x, y)) {
7538                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7539                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7540                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7541                  gatingPiece = boards[currentMove][fromY][fromX];
7542                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7543                 fromX = x;
7544                 fromY = y; dragging = 1;
7545                 ReportClick("lift", x, y);
7546                 MarkTargetSquares(0);
7547                 DragPieceBegin(xPix, yPix, FALSE);
7548                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7549                     promoSweep = defaultPromoChoice;
7550                     selectFlag = 0; lastX = xPix; lastY = yPix;
7551                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7552                 }
7553             }
7554            }
7555            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7556            second = FALSE;
7557         }
7558         // ignore clicks on holdings
7559         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7560     }
7561
7562     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7563         DragPieceEnd(xPix, yPix); dragging = 0;
7564         if(clearFlag) {
7565             // a deferred attempt to click-click move an empty square on top of a piece
7566             boards[currentMove][y][x] = EmptySquare;
7567             ClearHighlights();
7568             DrawPosition(FALSE, boards[currentMove]);
7569             fromX = fromY = -1; clearFlag = 0;
7570             return;
7571         }
7572         if (appData.animateDragging) {
7573             /* Undo animation damage if any */
7574             DrawPosition(FALSE, NULL);
7575         }
7576         if (second || sweepSelecting) {
7577             /* Second up/down in same square; just abort move */
7578             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7579             second = sweepSelecting = 0;
7580             fromX = fromY = -1;
7581             gatingPiece = EmptySquare;
7582             MarkTargetSquares(1);
7583             ClearHighlights();
7584             gotPremove = 0;
7585             ClearPremoveHighlights();
7586         } else {
7587             /* First upclick in same square; start click-click mode */
7588             SetHighlights(x, y, -1, -1);
7589         }
7590         return;
7591     }
7592
7593     clearFlag = 0;
7594
7595     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7596        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7597         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7598         DisplayMessage(_("only marked squares are legal"),"");
7599         DrawPosition(TRUE, NULL);
7600         return; // ignore to-click
7601     }
7602
7603     /* we now have a different from- and (possibly off-board) to-square */
7604     /* Completed move */
7605     if(!sweepSelecting) {
7606         toX = x;
7607         toY = y;
7608     }
7609
7610     piece = boards[currentMove][fromY][fromX];
7611
7612     saveAnimate = appData.animate;
7613     if (clickType == Press) {
7614         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7615         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7616             // must be Edit Position mode with empty-square selected
7617             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7618             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7619             return;
7620         }
7621         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7622             return;
7623         }
7624         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7625             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7626         } else
7627         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7628         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7629           if(appData.sweepSelect) {
7630             promoSweep = defaultPromoChoice;
7631             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7632             selectFlag = 0; lastX = xPix; lastY = yPix;
7633             Sweep(0); // Pawn that is going to promote: preview promotion piece
7634             sweepSelecting = 1;
7635             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7636             MarkTargetSquares(1);
7637           }
7638           return; // promo popup appears on up-click
7639         }
7640         /* Finish clickclick move */
7641         if (appData.animate || appData.highlightLastMove) {
7642             SetHighlights(fromX, fromY, toX, toY);
7643         } else {
7644             ClearHighlights();
7645         }
7646     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7647         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7648         if (appData.animate || appData.highlightLastMove) {
7649             SetHighlights(fromX, fromY, toX, toY);
7650         } else {
7651             ClearHighlights();
7652         }
7653     } else {
7654 #if 0
7655 // [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
7656         /* Finish drag move */
7657         if (appData.highlightLastMove) {
7658             SetHighlights(fromX, fromY, toX, toY);
7659         } else {
7660             ClearHighlights();
7661         }
7662 #endif
7663         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7664         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7665           dragging *= 2;            // flag button-less dragging if we are dragging
7666           MarkTargetSquares(1);
7667           if(x == killX && y == killY) killX = killY = -1; else {
7668             killX = x; killY = y;     //remeber this square as intermediate
7669             ReportClick("put", x, y); // and inform engine
7670             ReportClick("lift", x, y);
7671             MarkTargetSquares(0);
7672             return;
7673           }
7674         }
7675         DragPieceEnd(xPix, yPix); dragging = 0;
7676         /* Don't animate move and drag both */
7677         appData.animate = FALSE;
7678     }
7679
7680     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7681     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7682         ChessSquare piece = boards[currentMove][fromY][fromX];
7683         if(gameMode == EditPosition && piece != EmptySquare &&
7684            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7685             int n;
7686
7687             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7688                 n = PieceToNumber(piece - (int)BlackPawn);
7689                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7690                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7691                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7692             } else
7693             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7694                 n = PieceToNumber(piece);
7695                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7696                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7697                 boards[currentMove][n][BOARD_WIDTH-2]++;
7698             }
7699             boards[currentMove][fromY][fromX] = EmptySquare;
7700         }
7701         ClearHighlights();
7702         fromX = fromY = -1;
7703         MarkTargetSquares(1);
7704         DrawPosition(TRUE, boards[currentMove]);
7705         return;
7706     }
7707
7708     // off-board moves should not be highlighted
7709     if(x < 0 || y < 0) ClearHighlights();
7710     else ReportClick("put", x, y);
7711
7712     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7713
7714     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7715         SetHighlights(fromX, fromY, toX, toY);
7716         MarkTargetSquares(1);
7717         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7718             // [HGM] super: promotion to captured piece selected from holdings
7719             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7720             promotionChoice = TRUE;
7721             // kludge follows to temporarily execute move on display, without promoting yet
7722             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7723             boards[currentMove][toY][toX] = p;
7724             DrawPosition(FALSE, boards[currentMove]);
7725             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7726             boards[currentMove][toY][toX] = q;
7727             DisplayMessage("Click in holdings to choose piece", "");
7728             return;
7729         }
7730         PromotionPopUp(promoChoice);
7731     } else {
7732         int oldMove = currentMove;
7733         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7734         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7735         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7736         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7737            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7738             DrawPosition(TRUE, boards[currentMove]);
7739         MarkTargetSquares(1);
7740         fromX = fromY = -1;
7741     }
7742     appData.animate = saveAnimate;
7743     if (appData.animate || appData.animateDragging) {
7744         /* Undo animation damage if needed */
7745         DrawPosition(FALSE, NULL);
7746     }
7747 }
7748
7749 int
7750 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7751 {   // front-end-free part taken out of PieceMenuPopup
7752     int whichMenu; int xSqr, ySqr;
7753
7754     if(seekGraphUp) { // [HGM] seekgraph
7755         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7756         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7757         return -2;
7758     }
7759
7760     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7761          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7762         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7763         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7764         if(action == Press)   {
7765             originalFlip = flipView;
7766             flipView = !flipView; // temporarily flip board to see game from partners perspective
7767             DrawPosition(TRUE, partnerBoard);
7768             DisplayMessage(partnerStatus, "");
7769             partnerUp = TRUE;
7770         } else if(action == Release) {
7771             flipView = originalFlip;
7772             DrawPosition(TRUE, boards[currentMove]);
7773             partnerUp = FALSE;
7774         }
7775         return -2;
7776     }
7777
7778     xSqr = EventToSquare(x, BOARD_WIDTH);
7779     ySqr = EventToSquare(y, BOARD_HEIGHT);
7780     if (action == Release) {
7781         if(pieceSweep != EmptySquare) {
7782             EditPositionMenuEvent(pieceSweep, toX, toY);
7783             pieceSweep = EmptySquare;
7784         } else UnLoadPV(); // [HGM] pv
7785     }
7786     if (action != Press) return -2; // return code to be ignored
7787     switch (gameMode) {
7788       case IcsExamining:
7789         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7790       case EditPosition:
7791         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7792         if (xSqr < 0 || ySqr < 0) return -1;
7793         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7794         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7795         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7796         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7797         NextPiece(0);
7798         return 2; // grab
7799       case IcsObserving:
7800         if(!appData.icsEngineAnalyze) return -1;
7801       case IcsPlayingWhite:
7802       case IcsPlayingBlack:
7803         if(!appData.zippyPlay) goto noZip;
7804       case AnalyzeMode:
7805       case AnalyzeFile:
7806       case MachinePlaysWhite:
7807       case MachinePlaysBlack:
7808       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7809         if (!appData.dropMenu) {
7810           LoadPV(x, y);
7811           return 2; // flag front-end to grab mouse events
7812         }
7813         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7814            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7815       case EditGame:
7816       noZip:
7817         if (xSqr < 0 || ySqr < 0) return -1;
7818         if (!appData.dropMenu || appData.testLegality &&
7819             gameInfo.variant != VariantBughouse &&
7820             gameInfo.variant != VariantCrazyhouse) return -1;
7821         whichMenu = 1; // drop menu
7822         break;
7823       default:
7824         return -1;
7825     }
7826
7827     if (((*fromX = xSqr) < 0) ||
7828         ((*fromY = ySqr) < 0)) {
7829         *fromX = *fromY = -1;
7830         return -1;
7831     }
7832     if (flipView)
7833       *fromX = BOARD_WIDTH - 1 - *fromX;
7834     else
7835       *fromY = BOARD_HEIGHT - 1 - *fromY;
7836
7837     return whichMenu;
7838 }
7839
7840 void
7841 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7842 {
7843 //    char * hint = lastHint;
7844     FrontEndProgramStats stats;
7845
7846     stats.which = cps == &first ? 0 : 1;
7847     stats.depth = cpstats->depth;
7848     stats.nodes = cpstats->nodes;
7849     stats.score = cpstats->score;
7850     stats.time = cpstats->time;
7851     stats.pv = cpstats->movelist;
7852     stats.hint = lastHint;
7853     stats.an_move_index = 0;
7854     stats.an_move_count = 0;
7855
7856     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7857         stats.hint = cpstats->move_name;
7858         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7859         stats.an_move_count = cpstats->nr_moves;
7860     }
7861
7862     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
7863
7864     SetProgramStats( &stats );
7865 }
7866
7867 void
7868 ClearEngineOutputPane (int which)
7869 {
7870     static FrontEndProgramStats dummyStats;
7871     dummyStats.which = which;
7872     dummyStats.pv = "#";
7873     SetProgramStats( &dummyStats );
7874 }
7875
7876 #define MAXPLAYERS 500
7877
7878 char *
7879 TourneyStandings (int display)
7880 {
7881     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7882     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7883     char result, *p, *names[MAXPLAYERS];
7884
7885     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7886         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7887     names[0] = p = strdup(appData.participants);
7888     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7889
7890     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7891
7892     while(result = appData.results[nr]) {
7893         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7894         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7895         wScore = bScore = 0;
7896         switch(result) {
7897           case '+': wScore = 2; break;
7898           case '-': bScore = 2; break;
7899           case '=': wScore = bScore = 1; break;
7900           case ' ':
7901           case '*': return strdup("busy"); // tourney not finished
7902         }
7903         score[w] += wScore;
7904         score[b] += bScore;
7905         games[w]++;
7906         games[b]++;
7907         nr++;
7908     }
7909     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7910     for(w=0; w<nPlayers; w++) {
7911         bScore = -1;
7912         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7913         ranking[w] = b; points[w] = bScore; score[b] = -2;
7914     }
7915     p = malloc(nPlayers*34+1);
7916     for(w=0; w<nPlayers && w<display; w++)
7917         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7918     free(names[0]);
7919     return p;
7920 }
7921
7922 void
7923 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7924 {       // count all piece types
7925         int p, f, r;
7926         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7927         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7928         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7929                 p = board[r][f];
7930                 pCnt[p]++;
7931                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7932                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7933                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7934                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7935                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7936                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7937         }
7938 }
7939
7940 int
7941 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7942 {
7943         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7944         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7945
7946         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7947         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7948         if(myPawns == 2 && nMine == 3) // KPP
7949             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7950         if(myPawns == 1 && nMine == 2) // KP
7951             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7952         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7953             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7954         if(myPawns) return FALSE;
7955         if(pCnt[WhiteRook+side])
7956             return pCnt[BlackRook-side] ||
7957                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7958                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7959                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7960         if(pCnt[WhiteCannon+side]) {
7961             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7962             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7963         }
7964         if(pCnt[WhiteKnight+side])
7965             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7966         return FALSE;
7967 }
7968
7969 int
7970 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7971 {
7972         VariantClass v = gameInfo.variant;
7973
7974         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7975         if(v == VariantShatranj) return TRUE; // always winnable through baring
7976         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7977         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7978
7979         if(v == VariantXiangqi) {
7980                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7981
7982                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7983                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7984                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7985                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7986                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7987                 if(stale) // we have at least one last-rank P plus perhaps C
7988                     return majors // KPKX
7989                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7990                 else // KCA*E*
7991                     return pCnt[WhiteFerz+side] // KCAK
7992                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7993                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7994                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7995
7996         } else if(v == VariantKnightmate) {
7997                 if(nMine == 1) return FALSE;
7998                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7999         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8000                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8001
8002                 if(nMine == 1) return FALSE; // bare King
8003                 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
8004                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8005                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8006                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8007                 if(pCnt[WhiteKnight+side])
8008                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8009                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8010                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8011                 if(nBishops)
8012                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8013                 if(pCnt[WhiteAlfil+side])
8014                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8015                 if(pCnt[WhiteWazir+side])
8016                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8017         }
8018
8019         return TRUE;
8020 }
8021
8022 int
8023 CompareWithRights (Board b1, Board b2)
8024 {
8025     int rights = 0;
8026     if(!CompareBoards(b1, b2)) return FALSE;
8027     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8028     /* compare castling rights */
8029     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8030            rights++; /* King lost rights, while rook still had them */
8031     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8032         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8033            rights++; /* but at least one rook lost them */
8034     }
8035     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8036            rights++;
8037     if( b1[CASTLING][5] != NoRights ) {
8038         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8039            rights++;
8040     }
8041     return rights == 0;
8042 }
8043
8044 int
8045 Adjudicate (ChessProgramState *cps)
8046 {       // [HGM] some adjudications useful with buggy engines
8047         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8048         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8049         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8050         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8051         int k, drop, count = 0; static int bare = 1;
8052         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8053         Boolean canAdjudicate = !appData.icsActive;
8054
8055         // most tests only when we understand the game, i.e. legality-checking on
8056             if( appData.testLegality )
8057             {   /* [HGM] Some more adjudications for obstinate engines */
8058                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8059                 static int moveCount = 6;
8060                 ChessMove result;
8061                 char *reason = NULL;
8062
8063                 /* Count what is on board. */
8064                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8065
8066                 /* Some material-based adjudications that have to be made before stalemate test */
8067                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8068                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8069                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8070                      if(canAdjudicate && appData.checkMates) {
8071                          if(engineOpponent)
8072                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8073                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8074                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8075                          return 1;
8076                      }
8077                 }
8078
8079                 /* Bare King in Shatranj (loses) or Losers (wins) */
8080                 if( nrW == 1 || nrB == 1) {
8081                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8082                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8083                      if(canAdjudicate && appData.checkMates) {
8084                          if(engineOpponent)
8085                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8086                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8087                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8088                          return 1;
8089                      }
8090                   } else
8091                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8092                   {    /* bare King */
8093                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8094                         if(canAdjudicate && appData.checkMates) {
8095                             /* but only adjudicate if adjudication enabled */
8096                             if(engineOpponent)
8097                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8098                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8099                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8100                             return 1;
8101                         }
8102                   }
8103                 } else bare = 1;
8104
8105
8106             // don't wait for engine to announce game end if we can judge ourselves
8107             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8108               case MT_CHECK:
8109                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8110                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8111                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8112                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8113                             checkCnt++;
8114                         if(checkCnt >= 2) {
8115                             reason = "Xboard adjudication: 3rd check";
8116                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8117                             break;
8118                         }
8119                     }
8120                 }
8121               case MT_NONE:
8122               default:
8123                 break;
8124               case MT_STEALMATE:
8125               case MT_STALEMATE:
8126               case MT_STAINMATE:
8127                 reason = "Xboard adjudication: Stalemate";
8128                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8129                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8130                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8131                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8132                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8133                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8134                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8135                                                                         EP_CHECKMATE : EP_WINS);
8136                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8137                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8138                 }
8139                 break;
8140               case MT_CHECKMATE:
8141                 reason = "Xboard adjudication: Checkmate";
8142                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8143                 if(gameInfo.variant == VariantShogi) {
8144                     if(forwardMostMove > backwardMostMove
8145                        && moveList[forwardMostMove-1][1] == '@'
8146                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8147                         reason = "XBoard adjudication: pawn-drop mate";
8148                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8149                     }
8150                 }
8151                 break;
8152             }
8153
8154                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8155                     case EP_STALEMATE:
8156                         result = GameIsDrawn; break;
8157                     case EP_CHECKMATE:
8158                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8159                     case EP_WINS:
8160                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8161                     default:
8162                         result = EndOfFile;
8163                 }
8164                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8165                     if(engineOpponent)
8166                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8167                     GameEnds( result, reason, GE_XBOARD );
8168                     return 1;
8169                 }
8170
8171                 /* Next absolutely insufficient mating material. */
8172                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8173                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8174                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8175
8176                      /* always flag draws, for judging claims */
8177                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8178
8179                      if(canAdjudicate && appData.materialDraws) {
8180                          /* but only adjudicate them if adjudication enabled */
8181                          if(engineOpponent) {
8182                            SendToProgram("force\n", engineOpponent); // suppress reply
8183                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8184                          }
8185                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8186                          return 1;
8187                      }
8188                 }
8189
8190                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8191                 if(gameInfo.variant == VariantXiangqi ?
8192                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8193                  : nrW + nrB == 4 &&
8194                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8195                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8196                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8197                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8198                    ) ) {
8199                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8200                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8201                           if(engineOpponent) {
8202                             SendToProgram("force\n", engineOpponent); // suppress reply
8203                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8204                           }
8205                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8206                           return 1;
8207                      }
8208                 } else moveCount = 6;
8209             }
8210
8211         // Repetition draws and 50-move rule can be applied independently of legality testing
8212
8213                 /* Check for rep-draws */
8214                 count = 0;
8215                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8216                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8217                 for(k = forwardMostMove-2;
8218                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8219                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8220                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8221                     k-=2)
8222                 {   int rights=0;
8223                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8224                         /* compare castling rights */
8225                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8226                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8227                                 rights++; /* King lost rights, while rook still had them */
8228                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8229                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8230                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8231                                    rights++; /* but at least one rook lost them */
8232                         }
8233                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8234                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8235                                 rights++;
8236                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8237                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8238                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8239                                    rights++;
8240                         }
8241                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8242                             && appData.drawRepeats > 1) {
8243                              /* adjudicate after user-specified nr of repeats */
8244                              int result = GameIsDrawn;
8245                              char *details = "XBoard adjudication: repetition draw";
8246                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8247                                 // [HGM] xiangqi: check for forbidden perpetuals
8248                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8249                                 for(m=forwardMostMove; m>k; m-=2) {
8250                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8251                                         ourPerpetual = 0; // the current mover did not always check
8252                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8253                                         hisPerpetual = 0; // the opponent did not always check
8254                                 }
8255                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8256                                                                         ourPerpetual, hisPerpetual);
8257                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8258                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8259                                     details = "Xboard adjudication: perpetual checking";
8260                                 } else
8261                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8262                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8263                                 } else
8264                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8265                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8266                                         result = BlackWins;
8267                                         details = "Xboard adjudication: repetition";
8268                                     }
8269                                 } else // it must be XQ
8270                                 // Now check for perpetual chases
8271                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8272                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8273                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8274                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8275                                         static char resdet[MSG_SIZ];
8276                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8277                                         details = resdet;
8278                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8279                                     } else
8280                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8281                                         break; // Abort repetition-checking loop.
8282                                 }
8283                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8284                              }
8285                              if(engineOpponent) {
8286                                SendToProgram("force\n", engineOpponent); // suppress reply
8287                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8288                              }
8289                              GameEnds( result, details, GE_XBOARD );
8290                              return 1;
8291                         }
8292                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8293                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8294                     }
8295                 }
8296
8297                 /* Now we test for 50-move draws. Determine ply count */
8298                 count = forwardMostMove;
8299                 /* look for last irreversble move */
8300                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8301                     count--;
8302                 /* if we hit starting position, add initial plies */
8303                 if( count == backwardMostMove )
8304                     count -= initialRulePlies;
8305                 count = forwardMostMove - count;
8306                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8307                         // adjust reversible move counter for checks in Xiangqi
8308                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8309                         if(i < backwardMostMove) i = backwardMostMove;
8310                         while(i <= forwardMostMove) {
8311                                 lastCheck = inCheck; // check evasion does not count
8312                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8313                                 if(inCheck || lastCheck) count--; // check does not count
8314                                 i++;
8315                         }
8316                 }
8317                 if( count >= 100)
8318                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8319                          /* this is used to judge if draw claims are legal */
8320                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8321                          if(engineOpponent) {
8322                            SendToProgram("force\n", engineOpponent); // suppress reply
8323                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8324                          }
8325                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8326                          return 1;
8327                 }
8328
8329                 /* if draw offer is pending, treat it as a draw claim
8330                  * when draw condition present, to allow engines a way to
8331                  * claim draws before making their move to avoid a race
8332                  * condition occurring after their move
8333                  */
8334                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8335                          char *p = NULL;
8336                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8337                              p = "Draw claim: 50-move rule";
8338                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8339                              p = "Draw claim: 3-fold repetition";
8340                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8341                              p = "Draw claim: insufficient mating material";
8342                          if( p != NULL && canAdjudicate) {
8343                              if(engineOpponent) {
8344                                SendToProgram("force\n", engineOpponent); // suppress reply
8345                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8346                              }
8347                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8348                              return 1;
8349                          }
8350                 }
8351
8352                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8353                     if(engineOpponent) {
8354                       SendToProgram("force\n", engineOpponent); // suppress reply
8355                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8356                     }
8357                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8358                     return 1;
8359                 }
8360         return 0;
8361 }
8362
8363 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8364 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8365 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8366
8367 static int
8368 BitbaseProbe ()
8369 {
8370     int pieces[10], squares[10], cnt=0, r, f, res;
8371     static int loaded;
8372     static PPROBE_EGBB probeBB;
8373     if(!appData.testLegality) return 10;
8374     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8375     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8376     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8377     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8378         ChessSquare piece = boards[forwardMostMove][r][f];
8379         int black = (piece >= BlackPawn);
8380         int type = piece - black*BlackPawn;
8381         if(piece == EmptySquare) continue;
8382         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8383         if(type == WhiteKing) type = WhiteQueen + 1;
8384         type = egbbCode[type];
8385         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8386         pieces[cnt] = type + black*6;
8387         if(++cnt > 5) return 11;
8388     }
8389     pieces[cnt] = squares[cnt] = 0;
8390     // probe EGBB
8391     if(loaded == 2) return 13; // loading failed before
8392     if(loaded == 0) {
8393         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8394         HMODULE lib;
8395         PLOAD_EGBB loadBB;
8396         loaded = 2; // prepare for failure
8397         if(!path) return 13; // no egbb installed
8398         strncpy(buf, path + 8, MSG_SIZ);
8399         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8400         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8401         lib = LoadLibrary(buf);
8402         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8403         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8404         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8405         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8406         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8407         loaded = 1; // success!
8408     }
8409     res = probeBB(forwardMostMove & 1, pieces, squares);
8410     return res > 0 ? 1 : res < 0 ? -1 : 0;
8411 }
8412
8413 char *
8414 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8415 {   // [HGM] book: this routine intercepts moves to simulate book replies
8416     char *bookHit = NULL;
8417
8418     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8419         char buf[MSG_SIZ];
8420         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8421         SendToProgram(buf, cps);
8422     }
8423     //first determine if the incoming move brings opponent into his book
8424     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8425         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8426     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8427     if(bookHit != NULL && !cps->bookSuspend) {
8428         // make sure opponent is not going to reply after receiving move to book position
8429         SendToProgram("force\n", cps);
8430         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8431     }
8432     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8433     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8434     // now arrange restart after book miss
8435     if(bookHit) {
8436         // after a book hit we never send 'go', and the code after the call to this routine
8437         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8438         char buf[MSG_SIZ], *move = bookHit;
8439         if(cps->useSAN) {
8440             int fromX, fromY, toX, toY;
8441             char promoChar;
8442             ChessMove moveType;
8443             move = buf + 30;
8444             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8445                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8446                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8447                                     PosFlags(forwardMostMove),
8448                                     fromY, fromX, toY, toX, promoChar, move);
8449             } else {
8450                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8451                 bookHit = NULL;
8452             }
8453         }
8454         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8455         SendToProgram(buf, cps);
8456         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8457     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8458         SendToProgram("go\n", cps);
8459         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8460     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8461         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8462             SendToProgram("go\n", cps);
8463         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8464     }
8465     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8466 }
8467
8468 int
8469 LoadError (char *errmess, ChessProgramState *cps)
8470 {   // unloads engine and switches back to -ncp mode if it was first
8471     if(cps->initDone) return FALSE;
8472     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8473     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8474     cps->pr = NoProc;
8475     if(cps == &first) {
8476         appData.noChessProgram = TRUE;
8477         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8478         gameMode = BeginningOfGame; ModeHighlight();
8479         SetNCPMode();
8480     }
8481     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8482     DisplayMessage("", ""); // erase waiting message
8483     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8484     return TRUE;
8485 }
8486
8487 char *savedMessage;
8488 ChessProgramState *savedState;
8489 void
8490 DeferredBookMove (void)
8491 {
8492         if(savedState->lastPing != savedState->lastPong)
8493                     ScheduleDelayedEvent(DeferredBookMove, 10);
8494         else
8495         HandleMachineMove(savedMessage, savedState);
8496 }
8497
8498 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8499 static ChessProgramState *stalledEngine;
8500 static char stashedInputMove[MSG_SIZ];
8501
8502 void
8503 HandleMachineMove (char *message, ChessProgramState *cps)
8504 {
8505     static char firstLeg[20];
8506     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8507     char realname[MSG_SIZ];
8508     int fromX, fromY, toX, toY;
8509     ChessMove moveType;
8510     char promoChar, roar;
8511     char *p, *pv=buf1;
8512     int machineWhite, oldError;
8513     char *bookHit;
8514
8515     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8516         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8517         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8518             DisplayError(_("Invalid pairing from pairing engine"), 0);
8519             return;
8520         }
8521         pairingReceived = 1;
8522         NextMatchGame();
8523         return; // Skim the pairing messages here.
8524     }
8525
8526     oldError = cps->userError; cps->userError = 0;
8527
8528 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8529     /*
8530      * Kludge to ignore BEL characters
8531      */
8532     while (*message == '\007') message++;
8533
8534     /*
8535      * [HGM] engine debug message: ignore lines starting with '#' character
8536      */
8537     if(cps->debug && *message == '#') return;
8538
8539     /*
8540      * Look for book output
8541      */
8542     if (cps == &first && bookRequested) {
8543         if (message[0] == '\t' || message[0] == ' ') {
8544             /* Part of the book output is here; append it */
8545             strcat(bookOutput, message);
8546             strcat(bookOutput, "  \n");
8547             return;
8548         } else if (bookOutput[0] != NULLCHAR) {
8549             /* All of book output has arrived; display it */
8550             char *p = bookOutput;
8551             while (*p != NULLCHAR) {
8552                 if (*p == '\t') *p = ' ';
8553                 p++;
8554             }
8555             DisplayInformation(bookOutput);
8556             bookRequested = FALSE;
8557             /* Fall through to parse the current output */
8558         }
8559     }
8560
8561     /*
8562      * Look for machine move.
8563      */
8564     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8565         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8566     {
8567         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8568             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8569             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8570             stalledEngine = cps;
8571             if(appData.ponderNextMove) { // bring opponent out of ponder
8572                 if(gameMode == TwoMachinesPlay) {
8573                     if(cps->other->pause)
8574                         PauseEngine(cps->other);
8575                     else
8576                         SendToProgram("easy\n", cps->other);
8577                 }
8578             }
8579             StopClocks();
8580             return;
8581         }
8582
8583         /* This method is only useful on engines that support ping */
8584         if (cps->lastPing != cps->lastPong) {
8585           if (gameMode == BeginningOfGame) {
8586             /* Extra move from before last new; ignore */
8587             if (appData.debugMode) {
8588                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8589             }
8590           } else {
8591             if (appData.debugMode) {
8592                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8593                         cps->which, gameMode);
8594             }
8595
8596             SendToProgram("undo\n", cps);
8597           }
8598           return;
8599         }
8600
8601         switch (gameMode) {
8602           case BeginningOfGame:
8603             /* Extra move from before last reset; ignore */
8604             if (appData.debugMode) {
8605                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8606             }
8607             return;
8608
8609           case EndOfGame:
8610           case IcsIdle:
8611           default:
8612             /* Extra move after we tried to stop.  The mode test is
8613                not a reliable way of detecting this problem, but it's
8614                the best we can do on engines that don't support ping.
8615             */
8616             if (appData.debugMode) {
8617                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8618                         cps->which, gameMode);
8619             }
8620             SendToProgram("undo\n", cps);
8621             return;
8622
8623           case MachinePlaysWhite:
8624           case IcsPlayingWhite:
8625             machineWhite = TRUE;
8626             break;
8627
8628           case MachinePlaysBlack:
8629           case IcsPlayingBlack:
8630             machineWhite = FALSE;
8631             break;
8632
8633           case TwoMachinesPlay:
8634             machineWhite = (cps->twoMachinesColor[0] == 'w');
8635             break;
8636         }
8637         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8638             if (appData.debugMode) {
8639                 fprintf(debugFP,
8640                         "Ignoring move out of turn by %s, gameMode %d"
8641                         ", forwardMost %d\n",
8642                         cps->which, gameMode, forwardMostMove);
8643             }
8644             return;
8645         }
8646
8647         if(cps->alphaRank) AlphaRank(machineMove, 4);
8648
8649         // [HGM] lion: (some very limited) support for Alien protocol
8650         killX = killY = -1;
8651         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8652             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8653             return;
8654         } else if(firstLeg[0]) { // there was a previous leg;
8655             // only support case where same piece makes two step (and don't even test that!)
8656             char buf[20], *p = machineMove+1, *q = buf+1, f;
8657             safeStrCpy(buf, machineMove, 20);
8658             while(isdigit(*q)) q++; // find start of to-square
8659             safeStrCpy(machineMove, firstLeg, 20);
8660             while(isdigit(*p)) p++;
8661             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8662             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8663             firstLeg[0] = NULLCHAR;
8664         }
8665
8666         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8667                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8668             /* Machine move could not be parsed; ignore it. */
8669           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8670                     machineMove, _(cps->which));
8671             DisplayMoveError(buf1);
8672             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8673                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8674             if (gameMode == TwoMachinesPlay) {
8675               GameEnds(machineWhite ? BlackWins : WhiteWins,
8676                        buf1, GE_XBOARD);
8677             }
8678             return;
8679         }
8680
8681         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8682         /* So we have to redo legality test with true e.p. status here,  */
8683         /* to make sure an illegal e.p. capture does not slip through,   */
8684         /* to cause a forfeit on a justified illegal-move complaint      */
8685         /* of the opponent.                                              */
8686         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8687            ChessMove moveType;
8688            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8689                              fromY, fromX, toY, toX, promoChar);
8690             if(moveType == IllegalMove) {
8691               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8692                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8693                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8694                            buf1, GE_XBOARD);
8695                 return;
8696            } else if(!appData.fischerCastling)
8697            /* [HGM] Kludge to handle engines that send FRC-style castling
8698               when they shouldn't (like TSCP-Gothic) */
8699            switch(moveType) {
8700              case WhiteASideCastleFR:
8701              case BlackASideCastleFR:
8702                toX+=2;
8703                currentMoveString[2]++;
8704                break;
8705              case WhiteHSideCastleFR:
8706              case BlackHSideCastleFR:
8707                toX--;
8708                currentMoveString[2]--;
8709                break;
8710              default: ; // nothing to do, but suppresses warning of pedantic compilers
8711            }
8712         }
8713         hintRequested = FALSE;
8714         lastHint[0] = NULLCHAR;
8715         bookRequested = FALSE;
8716         /* Program may be pondering now */
8717         cps->maybeThinking = TRUE;
8718         if (cps->sendTime == 2) cps->sendTime = 1;
8719         if (cps->offeredDraw) cps->offeredDraw--;
8720
8721         /* [AS] Save move info*/
8722         pvInfoList[ forwardMostMove ].score = programStats.score;
8723         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8724         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8725
8726         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8727
8728         /* Test suites abort the 'game' after one move */
8729         if(*appData.finger) {
8730            static FILE *f;
8731            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8732            if(!f) f = fopen(appData.finger, "w");
8733            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8734            else { DisplayFatalError("Bad output file", errno, 0); return; }
8735            free(fen);
8736            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8737         }
8738
8739         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8740         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8741             int count = 0;
8742
8743             while( count < adjudicateLossPlies ) {
8744                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8745
8746                 if( count & 1 ) {
8747                     score = -score; /* Flip score for winning side */
8748                 }
8749
8750                 if( score > appData.adjudicateLossThreshold ) {
8751                     break;
8752                 }
8753
8754                 count++;
8755             }
8756
8757             if( count >= adjudicateLossPlies ) {
8758                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8759
8760                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8761                     "Xboard adjudication",
8762                     GE_XBOARD );
8763
8764                 return;
8765             }
8766         }
8767
8768         if(Adjudicate(cps)) {
8769             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8770             return; // [HGM] adjudicate: for all automatic game ends
8771         }
8772
8773 #if ZIPPY
8774         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8775             first.initDone) {
8776           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8777                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8778                 SendToICS("draw ");
8779                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8780           }
8781           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8782           ics_user_moved = 1;
8783           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8784                 char buf[3*MSG_SIZ];
8785
8786                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8787                         programStats.score / 100.,
8788                         programStats.depth,
8789                         programStats.time / 100.,
8790                         (unsigned int)programStats.nodes,
8791                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8792                         programStats.movelist);
8793                 SendToICS(buf);
8794           }
8795         }
8796 #endif
8797
8798         /* [AS] Clear stats for next move */
8799         ClearProgramStats();
8800         thinkOutput[0] = NULLCHAR;
8801         hiddenThinkOutputState = 0;
8802
8803         bookHit = NULL;
8804         if (gameMode == TwoMachinesPlay) {
8805             /* [HGM] relaying draw offers moved to after reception of move */
8806             /* and interpreting offer as claim if it brings draw condition */
8807             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8808                 SendToProgram("draw\n", cps->other);
8809             }
8810             if (cps->other->sendTime) {
8811                 SendTimeRemaining(cps->other,
8812                                   cps->other->twoMachinesColor[0] == 'w');
8813             }
8814             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8815             if (firstMove && !bookHit) {
8816                 firstMove = FALSE;
8817                 if (cps->other->useColors) {
8818                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8819                 }
8820                 SendToProgram("go\n", cps->other);
8821             }
8822             cps->other->maybeThinking = TRUE;
8823         }
8824
8825         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8826
8827         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8828
8829         if (!pausing && appData.ringBellAfterMoves) {
8830             if(!roar) RingBell();
8831         }
8832
8833         /*
8834          * Reenable menu items that were disabled while
8835          * machine was thinking
8836          */
8837         if (gameMode != TwoMachinesPlay)
8838             SetUserThinkingEnables();
8839
8840         // [HGM] book: after book hit opponent has received move and is now in force mode
8841         // force the book reply into it, and then fake that it outputted this move by jumping
8842         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8843         if(bookHit) {
8844                 static char bookMove[MSG_SIZ]; // a bit generous?
8845
8846                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8847                 strcat(bookMove, bookHit);
8848                 message = bookMove;
8849                 cps = cps->other;
8850                 programStats.nodes = programStats.depth = programStats.time =
8851                 programStats.score = programStats.got_only_move = 0;
8852                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8853
8854                 if(cps->lastPing != cps->lastPong) {
8855                     savedMessage = message; // args for deferred call
8856                     savedState = cps;
8857                     ScheduleDelayedEvent(DeferredBookMove, 10);
8858                     return;
8859                 }
8860                 goto FakeBookMove;
8861         }
8862
8863         return;
8864     }
8865
8866     /* Set special modes for chess engines.  Later something general
8867      *  could be added here; for now there is just one kludge feature,
8868      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8869      *  when "xboard" is given as an interactive command.
8870      */
8871     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8872         cps->useSigint = FALSE;
8873         cps->useSigterm = FALSE;
8874     }
8875     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8876       ParseFeatures(message+8, cps);
8877       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8878     }
8879
8880     if (!strncmp(message, "setup ", 6) && 
8881         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8882           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8883                                         ) { // [HGM] allow first engine to define opening position
8884       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8885       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8886       *buf = NULLCHAR;
8887       if(sscanf(message, "setup (%s", buf) == 1) {
8888         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8889         ASSIGN(appData.pieceToCharTable, buf);
8890       }
8891       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8892       if(dummy >= 3) {
8893         while(message[s] && message[s++] != ' ');
8894         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8895            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8896             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8897             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8898           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8899           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8900           startedFromSetupPosition = FALSE;
8901         }
8902       }
8903       if(startedFromSetupPosition) return;
8904       ParseFEN(boards[0], &dummy, message+s, FALSE);
8905       DrawPosition(TRUE, boards[0]);
8906       CopyBoard(initialPosition, boards[0]);
8907       startedFromSetupPosition = TRUE;
8908       return;
8909     }
8910     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8911       ChessSquare piece = WhitePawn;
8912       char *p=buf2;
8913       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8914       piece += CharToPiece(*p) - WhitePawn;
8915       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8916       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8917       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8918       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8919       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8920       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8921                                                && gameInfo.variant != VariantGreat
8922                                                && gameInfo.variant != VariantFairy    ) return;
8923       if(piece < EmptySquare) {
8924         pieceDefs = TRUE;
8925         ASSIGN(pieceDesc[piece], buf1);
8926         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8927       }
8928       return;
8929     }
8930     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8931      * want this, I was asked to put it in, and obliged.
8932      */
8933     if (!strncmp(message, "setboard ", 9)) {
8934         Board initial_position;
8935
8936         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8937
8938         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8939             DisplayError(_("Bad FEN received from engine"), 0);
8940             return ;
8941         } else {
8942            Reset(TRUE, FALSE);
8943            CopyBoard(boards[0], initial_position);
8944            initialRulePlies = FENrulePlies;
8945            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8946            else gameMode = MachinePlaysBlack;
8947            DrawPosition(FALSE, boards[currentMove]);
8948         }
8949         return;
8950     }
8951
8952     /*
8953      * Look for communication commands
8954      */
8955     if (!strncmp(message, "telluser ", 9)) {
8956         if(message[9] == '\\' && message[10] == '\\')
8957             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8958         PlayTellSound();
8959         DisplayNote(message + 9);
8960         return;
8961     }
8962     if (!strncmp(message, "tellusererror ", 14)) {
8963         cps->userError = 1;
8964         if(message[14] == '\\' && message[15] == '\\')
8965             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8966         PlayTellSound();
8967         DisplayError(message + 14, 0);
8968         return;
8969     }
8970     if (!strncmp(message, "tellopponent ", 13)) {
8971       if (appData.icsActive) {
8972         if (loggedOn) {
8973           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8974           SendToICS(buf1);
8975         }
8976       } else {
8977         DisplayNote(message + 13);
8978       }
8979       return;
8980     }
8981     if (!strncmp(message, "tellothers ", 11)) {
8982       if (appData.icsActive) {
8983         if (loggedOn) {
8984           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8985           SendToICS(buf1);
8986         }
8987       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8988       return;
8989     }
8990     if (!strncmp(message, "tellall ", 8)) {
8991       if (appData.icsActive) {
8992         if (loggedOn) {
8993           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8994           SendToICS(buf1);
8995         }
8996       } else {
8997         DisplayNote(message + 8);
8998       }
8999       return;
9000     }
9001     if (strncmp(message, "warning", 7) == 0) {
9002         /* Undocumented feature, use tellusererror in new code */
9003         DisplayError(message, 0);
9004         return;
9005     }
9006     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9007         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9008         strcat(realname, " query");
9009         AskQuestion(realname, buf2, buf1, cps->pr);
9010         return;
9011     }
9012     /* Commands from the engine directly to ICS.  We don't allow these to be
9013      *  sent until we are logged on. Crafty kibitzes have been known to
9014      *  interfere with the login process.
9015      */
9016     if (loggedOn) {
9017         if (!strncmp(message, "tellics ", 8)) {
9018             SendToICS(message + 8);
9019             SendToICS("\n");
9020             return;
9021         }
9022         if (!strncmp(message, "tellicsnoalias ", 15)) {
9023             SendToICS(ics_prefix);
9024             SendToICS(message + 15);
9025             SendToICS("\n");
9026             return;
9027         }
9028         /* The following are for backward compatibility only */
9029         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9030             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9031             SendToICS(ics_prefix);
9032             SendToICS(message);
9033             SendToICS("\n");
9034             return;
9035         }
9036     }
9037     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9038         if(initPing == cps->lastPong) {
9039             if(gameInfo.variant == VariantUnknown) {
9040                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9041                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9042                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9043             }
9044             initPing = -1;
9045         }
9046         return;
9047     }
9048     if(!strncmp(message, "highlight ", 10)) {
9049         if(appData.testLegality && appData.markers) return;
9050         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9051         return;
9052     }
9053     if(!strncmp(message, "click ", 6)) {
9054         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9055         if(appData.testLegality || !appData.oneClick) return;
9056         sscanf(message+6, "%c%d%c", &f, &y, &c);
9057         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9058         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9059         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9060         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9061         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9062         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9063             LeftClick(Release, lastLeftX, lastLeftY);
9064         controlKey  = (c == ',');
9065         LeftClick(Press, x, y);
9066         LeftClick(Release, x, y);
9067         first.highlight = f;
9068         return;
9069     }
9070     /*
9071      * If the move is illegal, cancel it and redraw the board.
9072      * Also deal with other error cases.  Matching is rather loose
9073      * here to accommodate engines written before the spec.
9074      */
9075     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9076         strncmp(message, "Error", 5) == 0) {
9077         if (StrStr(message, "name") ||
9078             StrStr(message, "rating") || StrStr(message, "?") ||
9079             StrStr(message, "result") || StrStr(message, "board") ||
9080             StrStr(message, "bk") || StrStr(message, "computer") ||
9081             StrStr(message, "variant") || StrStr(message, "hint") ||
9082             StrStr(message, "random") || StrStr(message, "depth") ||
9083             StrStr(message, "accepted")) {
9084             return;
9085         }
9086         if (StrStr(message, "protover")) {
9087           /* Program is responding to input, so it's apparently done
9088              initializing, and this error message indicates it is
9089              protocol version 1.  So we don't need to wait any longer
9090              for it to initialize and send feature commands. */
9091           FeatureDone(cps, 1);
9092           cps->protocolVersion = 1;
9093           return;
9094         }
9095         cps->maybeThinking = FALSE;
9096
9097         if (StrStr(message, "draw")) {
9098             /* Program doesn't have "draw" command */
9099             cps->sendDrawOffers = 0;
9100             return;
9101         }
9102         if (cps->sendTime != 1 &&
9103             (StrStr(message, "time") || StrStr(message, "otim"))) {
9104           /* Program apparently doesn't have "time" or "otim" command */
9105           cps->sendTime = 0;
9106           return;
9107         }
9108         if (StrStr(message, "analyze")) {
9109             cps->analysisSupport = FALSE;
9110             cps->analyzing = FALSE;
9111 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9112             EditGameEvent(); // [HGM] try to preserve loaded game
9113             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9114             DisplayError(buf2, 0);
9115             return;
9116         }
9117         if (StrStr(message, "(no matching move)st")) {
9118           /* Special kludge for GNU Chess 4 only */
9119           cps->stKludge = TRUE;
9120           SendTimeControl(cps, movesPerSession, timeControl,
9121                           timeIncrement, appData.searchDepth,
9122                           searchTime);
9123           return;
9124         }
9125         if (StrStr(message, "(no matching move)sd")) {
9126           /* Special kludge for GNU Chess 4 only */
9127           cps->sdKludge = TRUE;
9128           SendTimeControl(cps, movesPerSession, timeControl,
9129                           timeIncrement, appData.searchDepth,
9130                           searchTime);
9131           return;
9132         }
9133         if (!StrStr(message, "llegal")) {
9134             return;
9135         }
9136         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9137             gameMode == IcsIdle) return;
9138         if (forwardMostMove <= backwardMostMove) return;
9139         if (pausing) PauseEvent();
9140       if(appData.forceIllegal) {
9141             // [HGM] illegal: machine refused move; force position after move into it
9142           SendToProgram("force\n", cps);
9143           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9144                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9145                 // when black is to move, while there might be nothing on a2 or black
9146                 // might already have the move. So send the board as if white has the move.
9147                 // But first we must change the stm of the engine, as it refused the last move
9148                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9149                 if(WhiteOnMove(forwardMostMove)) {
9150                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9151                     SendBoard(cps, forwardMostMove); // kludgeless board
9152                 } else {
9153                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9154                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9155                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9156                 }
9157           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9158             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9159                  gameMode == TwoMachinesPlay)
9160               SendToProgram("go\n", cps);
9161             return;
9162       } else
9163         if (gameMode == PlayFromGameFile) {
9164             /* Stop reading this game file */
9165             gameMode = EditGame;
9166             ModeHighlight();
9167         }
9168         /* [HGM] illegal-move claim should forfeit game when Xboard */
9169         /* only passes fully legal moves                            */
9170         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9171             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9172                                 "False illegal-move claim", GE_XBOARD );
9173             return; // do not take back move we tested as valid
9174         }
9175         currentMove = forwardMostMove-1;
9176         DisplayMove(currentMove-1); /* before DisplayMoveError */
9177         SwitchClocks(forwardMostMove-1); // [HGM] race
9178         DisplayBothClocks();
9179         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9180                 parseList[currentMove], _(cps->which));
9181         DisplayMoveError(buf1);
9182         DrawPosition(FALSE, boards[currentMove]);
9183
9184         SetUserThinkingEnables();
9185         return;
9186     }
9187     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9188         /* Program has a broken "time" command that
9189            outputs a string not ending in newline.
9190            Don't use it. */
9191         cps->sendTime = 0;
9192     }
9193     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9194         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9195             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9196     }
9197
9198     /*
9199      * If chess program startup fails, exit with an error message.
9200      * Attempts to recover here are futile. [HGM] Well, we try anyway
9201      */
9202     if ((StrStr(message, "unknown host") != NULL)
9203         || (StrStr(message, "No remote directory") != NULL)
9204         || (StrStr(message, "not found") != NULL)
9205         || (StrStr(message, "No such file") != NULL)
9206         || (StrStr(message, "can't alloc") != NULL)
9207         || (StrStr(message, "Permission denied") != NULL)) {
9208
9209         cps->maybeThinking = FALSE;
9210         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9211                 _(cps->which), cps->program, cps->host, message);
9212         RemoveInputSource(cps->isr);
9213         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9214             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9215             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9216         }
9217         return;
9218     }
9219
9220     /*
9221      * Look for hint output
9222      */
9223     if (sscanf(message, "Hint: %s", buf1) == 1) {
9224         if (cps == &first && hintRequested) {
9225             hintRequested = FALSE;
9226             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9227                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9228                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9229                                     PosFlags(forwardMostMove),
9230                                     fromY, fromX, toY, toX, promoChar, buf1);
9231                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9232                 DisplayInformation(buf2);
9233             } else {
9234                 /* Hint move could not be parsed!? */
9235               snprintf(buf2, sizeof(buf2),
9236                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9237                         buf1, _(cps->which));
9238                 DisplayError(buf2, 0);
9239             }
9240         } else {
9241           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9242         }
9243         return;
9244     }
9245
9246     /*
9247      * Ignore other messages if game is not in progress
9248      */
9249     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9250         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9251
9252     /*
9253      * look for win, lose, draw, or draw offer
9254      */
9255     if (strncmp(message, "1-0", 3) == 0) {
9256         char *p, *q, *r = "";
9257         p = strchr(message, '{');
9258         if (p) {
9259             q = strchr(p, '}');
9260             if (q) {
9261                 *q = NULLCHAR;
9262                 r = p + 1;
9263             }
9264         }
9265         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9266         return;
9267     } else if (strncmp(message, "0-1", 3) == 0) {
9268         char *p, *q, *r = "";
9269         p = strchr(message, '{');
9270         if (p) {
9271             q = strchr(p, '}');
9272             if (q) {
9273                 *q = NULLCHAR;
9274                 r = p + 1;
9275             }
9276         }
9277         /* Kludge for Arasan 4.1 bug */
9278         if (strcmp(r, "Black resigns") == 0) {
9279             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9280             return;
9281         }
9282         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9283         return;
9284     } else if (strncmp(message, "1/2", 3) == 0) {
9285         char *p, *q, *r = "";
9286         p = strchr(message, '{');
9287         if (p) {
9288             q = strchr(p, '}');
9289             if (q) {
9290                 *q = NULLCHAR;
9291                 r = p + 1;
9292             }
9293         }
9294
9295         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9296         return;
9297
9298     } else if (strncmp(message, "White resign", 12) == 0) {
9299         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9300         return;
9301     } else if (strncmp(message, "Black resign", 12) == 0) {
9302         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9303         return;
9304     } else if (strncmp(message, "White matches", 13) == 0 ||
9305                strncmp(message, "Black matches", 13) == 0   ) {
9306         /* [HGM] ignore GNUShogi noises */
9307         return;
9308     } else if (strncmp(message, "White", 5) == 0 &&
9309                message[5] != '(' &&
9310                StrStr(message, "Black") == NULL) {
9311         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9312         return;
9313     } else if (strncmp(message, "Black", 5) == 0 &&
9314                message[5] != '(') {
9315         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9316         return;
9317     } else if (strcmp(message, "resign") == 0 ||
9318                strcmp(message, "computer resigns") == 0) {
9319         switch (gameMode) {
9320           case MachinePlaysBlack:
9321           case IcsPlayingBlack:
9322             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9323             break;
9324           case MachinePlaysWhite:
9325           case IcsPlayingWhite:
9326             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9327             break;
9328           case TwoMachinesPlay:
9329             if (cps->twoMachinesColor[0] == 'w')
9330               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9331             else
9332               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9333             break;
9334           default:
9335             /* can't happen */
9336             break;
9337         }
9338         return;
9339     } else if (strncmp(message, "opponent mates", 14) == 0) {
9340         switch (gameMode) {
9341           case MachinePlaysBlack:
9342           case IcsPlayingBlack:
9343             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9344             break;
9345           case MachinePlaysWhite:
9346           case IcsPlayingWhite:
9347             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9348             break;
9349           case TwoMachinesPlay:
9350             if (cps->twoMachinesColor[0] == 'w')
9351               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9352             else
9353               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9354             break;
9355           default:
9356             /* can't happen */
9357             break;
9358         }
9359         return;
9360     } else if (strncmp(message, "computer mates", 14) == 0) {
9361         switch (gameMode) {
9362           case MachinePlaysBlack:
9363           case IcsPlayingBlack:
9364             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9365             break;
9366           case MachinePlaysWhite:
9367           case IcsPlayingWhite:
9368             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9369             break;
9370           case TwoMachinesPlay:
9371             if (cps->twoMachinesColor[0] == 'w')
9372               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9373             else
9374               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9375             break;
9376           default:
9377             /* can't happen */
9378             break;
9379         }
9380         return;
9381     } else if (strncmp(message, "checkmate", 9) == 0) {
9382         if (WhiteOnMove(forwardMostMove)) {
9383             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9384         } else {
9385             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9386         }
9387         return;
9388     } else if (strstr(message, "Draw") != NULL ||
9389                strstr(message, "game is a draw") != NULL) {
9390         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9391         return;
9392     } else if (strstr(message, "offer") != NULL &&
9393                strstr(message, "draw") != NULL) {
9394 #if ZIPPY
9395         if (appData.zippyPlay && first.initDone) {
9396             /* Relay offer to ICS */
9397             SendToICS(ics_prefix);
9398             SendToICS("draw\n");
9399         }
9400 #endif
9401         cps->offeredDraw = 2; /* valid until this engine moves twice */
9402         if (gameMode == TwoMachinesPlay) {
9403             if (cps->other->offeredDraw) {
9404                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9405             /* [HGM] in two-machine mode we delay relaying draw offer      */
9406             /* until after we also have move, to see if it is really claim */
9407             }
9408         } else if (gameMode == MachinePlaysWhite ||
9409                    gameMode == MachinePlaysBlack) {
9410           if (userOfferedDraw) {
9411             DisplayInformation(_("Machine accepts your draw offer"));
9412             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9413           } else {
9414             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9415           }
9416         }
9417     }
9418
9419
9420     /*
9421      * Look for thinking output
9422      */
9423     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9424           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9425                                 ) {
9426         int plylev, mvleft, mvtot, curscore, time;
9427         char mvname[MOVE_LEN];
9428         u64 nodes; // [DM]
9429         char plyext;
9430         int ignore = FALSE;
9431         int prefixHint = FALSE;
9432         mvname[0] = NULLCHAR;
9433
9434         switch (gameMode) {
9435           case MachinePlaysBlack:
9436           case IcsPlayingBlack:
9437             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9438             break;
9439           case MachinePlaysWhite:
9440           case IcsPlayingWhite:
9441             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9442             break;
9443           case AnalyzeMode:
9444           case AnalyzeFile:
9445             break;
9446           case IcsObserving: /* [DM] icsEngineAnalyze */
9447             if (!appData.icsEngineAnalyze) ignore = TRUE;
9448             break;
9449           case TwoMachinesPlay:
9450             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9451                 ignore = TRUE;
9452             }
9453             break;
9454           default:
9455             ignore = TRUE;
9456             break;
9457         }
9458
9459         if (!ignore) {
9460             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9461             buf1[0] = NULLCHAR;
9462             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9463                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9464
9465                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9466                     nodes += u64Const(0x100000000);
9467
9468                 if (plyext != ' ' && plyext != '\t') {
9469                     time *= 100;
9470                 }
9471
9472                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9473                 if( cps->scoreIsAbsolute &&
9474                     ( gameMode == MachinePlaysBlack ||
9475                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9476                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9477                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9478                      !WhiteOnMove(currentMove)
9479                     ) )
9480                 {
9481                     curscore = -curscore;
9482                 }
9483
9484                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9485
9486                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9487                         char buf[MSG_SIZ];
9488                         FILE *f;
9489                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9490                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9491                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9492                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9493                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9494                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9495                                 fclose(f);
9496                         }
9497                         else
9498                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9499                           DisplayError(_("failed writing PV"), 0);
9500                 }
9501
9502                 tempStats.depth = plylev;
9503                 tempStats.nodes = nodes;
9504                 tempStats.time = time;
9505                 tempStats.score = curscore;
9506                 tempStats.got_only_move = 0;
9507
9508                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9509                         int ticklen;
9510
9511                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9512                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9513                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9514                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9515                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9516                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9517                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9518                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9519                 }
9520
9521                 /* Buffer overflow protection */
9522                 if (pv[0] != NULLCHAR) {
9523                     if (strlen(pv) >= sizeof(tempStats.movelist)
9524                         && appData.debugMode) {
9525                         fprintf(debugFP,
9526                                 "PV is too long; using the first %u bytes.\n",
9527                                 (unsigned) sizeof(tempStats.movelist) - 1);
9528                     }
9529
9530                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9531                 } else {
9532                     sprintf(tempStats.movelist, " no PV\n");
9533                 }
9534
9535                 if (tempStats.seen_stat) {
9536                     tempStats.ok_to_send = 1;
9537                 }
9538
9539                 if (strchr(tempStats.movelist, '(') != NULL) {
9540                     tempStats.line_is_book = 1;
9541                     tempStats.nr_moves = 0;
9542                     tempStats.moves_left = 0;
9543                 } else {
9544                     tempStats.line_is_book = 0;
9545                 }
9546
9547                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9548                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9549
9550                 SendProgramStatsToFrontend( cps, &tempStats );
9551
9552                 /*
9553                     [AS] Protect the thinkOutput buffer from overflow... this
9554                     is only useful if buf1 hasn't overflowed first!
9555                 */
9556                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9557                          plylev,
9558                          (gameMode == TwoMachinesPlay ?
9559                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9560                          ((double) curscore) / 100.0,
9561                          prefixHint ? lastHint : "",
9562                          prefixHint ? " " : "" );
9563
9564                 if( buf1[0] != NULLCHAR ) {
9565                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9566
9567                     if( strlen(pv) > max_len ) {
9568                         if( appData.debugMode) {
9569                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9570                         }
9571                         pv[max_len+1] = '\0';
9572                     }
9573
9574                     strcat( thinkOutput, pv);
9575                 }
9576
9577                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9578                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9579                     DisplayMove(currentMove - 1);
9580                 }
9581                 return;
9582
9583             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9584                 /* crafty (9.25+) says "(only move) <move>"
9585                  * if there is only 1 legal move
9586                  */
9587                 sscanf(p, "(only move) %s", buf1);
9588                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9589                 sprintf(programStats.movelist, "%s (only move)", buf1);
9590                 programStats.depth = 1;
9591                 programStats.nr_moves = 1;
9592                 programStats.moves_left = 1;
9593                 programStats.nodes = 1;
9594                 programStats.time = 1;
9595                 programStats.got_only_move = 1;
9596
9597                 /* Not really, but we also use this member to
9598                    mean "line isn't going to change" (Crafty
9599                    isn't searching, so stats won't change) */
9600                 programStats.line_is_book = 1;
9601
9602                 SendProgramStatsToFrontend( cps, &programStats );
9603
9604                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9605                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9606                     DisplayMove(currentMove - 1);
9607                 }
9608                 return;
9609             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9610                               &time, &nodes, &plylev, &mvleft,
9611                               &mvtot, mvname) >= 5) {
9612                 /* The stat01: line is from Crafty (9.29+) in response
9613                    to the "." command */
9614                 programStats.seen_stat = 1;
9615                 cps->maybeThinking = TRUE;
9616
9617                 if (programStats.got_only_move || !appData.periodicUpdates)
9618                   return;
9619
9620                 programStats.depth = plylev;
9621                 programStats.time = time;
9622                 programStats.nodes = nodes;
9623                 programStats.moves_left = mvleft;
9624                 programStats.nr_moves = mvtot;
9625                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9626                 programStats.ok_to_send = 1;
9627                 programStats.movelist[0] = '\0';
9628
9629                 SendProgramStatsToFrontend( cps, &programStats );
9630
9631                 return;
9632
9633             } else if (strncmp(message,"++",2) == 0) {
9634                 /* Crafty 9.29+ outputs this */
9635                 programStats.got_fail = 2;
9636                 return;
9637
9638             } else if (strncmp(message,"--",2) == 0) {
9639                 /* Crafty 9.29+ outputs this */
9640                 programStats.got_fail = 1;
9641                 return;
9642
9643             } else if (thinkOutput[0] != NULLCHAR &&
9644                        strncmp(message, "    ", 4) == 0) {
9645                 unsigned message_len;
9646
9647                 p = message;
9648                 while (*p && *p == ' ') p++;
9649
9650                 message_len = strlen( p );
9651
9652                 /* [AS] Avoid buffer overflow */
9653                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9654                     strcat(thinkOutput, " ");
9655                     strcat(thinkOutput, p);
9656                 }
9657
9658                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9659                     strcat(programStats.movelist, " ");
9660                     strcat(programStats.movelist, p);
9661                 }
9662
9663                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9664                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9665                     DisplayMove(currentMove - 1);
9666                 }
9667                 return;
9668             }
9669         }
9670         else {
9671             buf1[0] = NULLCHAR;
9672
9673             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9674                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9675             {
9676                 ChessProgramStats cpstats;
9677
9678                 if (plyext != ' ' && plyext != '\t') {
9679                     time *= 100;
9680                 }
9681
9682                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9683                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9684                     curscore = -curscore;
9685                 }
9686
9687                 cpstats.depth = plylev;
9688                 cpstats.nodes = nodes;
9689                 cpstats.time = time;
9690                 cpstats.score = curscore;
9691                 cpstats.got_only_move = 0;
9692                 cpstats.movelist[0] = '\0';
9693
9694                 if (buf1[0] != NULLCHAR) {
9695                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9696                 }
9697
9698                 cpstats.ok_to_send = 0;
9699                 cpstats.line_is_book = 0;
9700                 cpstats.nr_moves = 0;
9701                 cpstats.moves_left = 0;
9702
9703                 SendProgramStatsToFrontend( cps, &cpstats );
9704             }
9705         }
9706     }
9707 }
9708
9709
9710 /* Parse a game score from the character string "game", and
9711    record it as the history of the current game.  The game
9712    score is NOT assumed to start from the standard position.
9713    The display is not updated in any way.
9714    */
9715 void
9716 ParseGameHistory (char *game)
9717 {
9718     ChessMove moveType;
9719     int fromX, fromY, toX, toY, boardIndex;
9720     char promoChar;
9721     char *p, *q;
9722     char buf[MSG_SIZ];
9723
9724     if (appData.debugMode)
9725       fprintf(debugFP, "Parsing game history: %s\n", game);
9726
9727     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9728     gameInfo.site = StrSave(appData.icsHost);
9729     gameInfo.date = PGNDate();
9730     gameInfo.round = StrSave("-");
9731
9732     /* Parse out names of players */
9733     while (*game == ' ') game++;
9734     p = buf;
9735     while (*game != ' ') *p++ = *game++;
9736     *p = NULLCHAR;
9737     gameInfo.white = StrSave(buf);
9738     while (*game == ' ') game++;
9739     p = buf;
9740     while (*game != ' ' && *game != '\n') *p++ = *game++;
9741     *p = NULLCHAR;
9742     gameInfo.black = StrSave(buf);
9743
9744     /* Parse moves */
9745     boardIndex = blackPlaysFirst ? 1 : 0;
9746     yynewstr(game);
9747     for (;;) {
9748         yyboardindex = boardIndex;
9749         moveType = (ChessMove) Myylex();
9750         switch (moveType) {
9751           case IllegalMove:             /* maybe suicide chess, etc. */
9752   if (appData.debugMode) {
9753     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9754     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9755     setbuf(debugFP, NULL);
9756   }
9757           case WhitePromotion:
9758           case BlackPromotion:
9759           case WhiteNonPromotion:
9760           case BlackNonPromotion:
9761           case NormalMove:
9762           case FirstLeg:
9763           case WhiteCapturesEnPassant:
9764           case BlackCapturesEnPassant:
9765           case WhiteKingSideCastle:
9766           case WhiteQueenSideCastle:
9767           case BlackKingSideCastle:
9768           case BlackQueenSideCastle:
9769           case WhiteKingSideCastleWild:
9770           case WhiteQueenSideCastleWild:
9771           case BlackKingSideCastleWild:
9772           case BlackQueenSideCastleWild:
9773           /* PUSH Fabien */
9774           case WhiteHSideCastleFR:
9775           case WhiteASideCastleFR:
9776           case BlackHSideCastleFR:
9777           case BlackASideCastleFR:
9778           /* POP Fabien */
9779             fromX = currentMoveString[0] - AAA;
9780             fromY = currentMoveString[1] - ONE;
9781             toX = currentMoveString[2] - AAA;
9782             toY = currentMoveString[3] - ONE;
9783             promoChar = currentMoveString[4];
9784             break;
9785           case WhiteDrop:
9786           case BlackDrop:
9787             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9788             fromX = moveType == WhiteDrop ?
9789               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9790             (int) CharToPiece(ToLower(currentMoveString[0]));
9791             fromY = DROP_RANK;
9792             toX = currentMoveString[2] - AAA;
9793             toY = currentMoveString[3] - ONE;
9794             promoChar = NULLCHAR;
9795             break;
9796           case AmbiguousMove:
9797             /* bug? */
9798             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9799   if (appData.debugMode) {
9800     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9801     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9802     setbuf(debugFP, NULL);
9803   }
9804             DisplayError(buf, 0);
9805             return;
9806           case ImpossibleMove:
9807             /* bug? */
9808             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9809   if (appData.debugMode) {
9810     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9811     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9812     setbuf(debugFP, NULL);
9813   }
9814             DisplayError(buf, 0);
9815             return;
9816           case EndOfFile:
9817             if (boardIndex < backwardMostMove) {
9818                 /* Oops, gap.  How did that happen? */
9819                 DisplayError(_("Gap in move list"), 0);
9820                 return;
9821             }
9822             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9823             if (boardIndex > forwardMostMove) {
9824                 forwardMostMove = boardIndex;
9825             }
9826             return;
9827           case ElapsedTime:
9828             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9829                 strcat(parseList[boardIndex-1], " ");
9830                 strcat(parseList[boardIndex-1], yy_text);
9831             }
9832             continue;
9833           case Comment:
9834           case PGNTag:
9835           case NAG:
9836           default:
9837             /* ignore */
9838             continue;
9839           case WhiteWins:
9840           case BlackWins:
9841           case GameIsDrawn:
9842           case GameUnfinished:
9843             if (gameMode == IcsExamining) {
9844                 if (boardIndex < backwardMostMove) {
9845                     /* Oops, gap.  How did that happen? */
9846                     return;
9847                 }
9848                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9849                 return;
9850             }
9851             gameInfo.result = moveType;
9852             p = strchr(yy_text, '{');
9853             if (p == NULL) p = strchr(yy_text, '(');
9854             if (p == NULL) {
9855                 p = yy_text;
9856                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9857             } else {
9858                 q = strchr(p, *p == '{' ? '}' : ')');
9859                 if (q != NULL) *q = NULLCHAR;
9860                 p++;
9861             }
9862             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9863             gameInfo.resultDetails = StrSave(p);
9864             continue;
9865         }
9866         if (boardIndex >= forwardMostMove &&
9867             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9868             backwardMostMove = blackPlaysFirst ? 1 : 0;
9869             return;
9870         }
9871         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9872                                  fromY, fromX, toY, toX, promoChar,
9873                                  parseList[boardIndex]);
9874         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9875         /* currentMoveString is set as a side-effect of yylex */
9876         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9877         strcat(moveList[boardIndex], "\n");
9878         boardIndex++;
9879         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9880         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9881           case MT_NONE:
9882           case MT_STALEMATE:
9883           default:
9884             break;
9885           case MT_CHECK:
9886             if(!IS_SHOGI(gameInfo.variant))
9887                 strcat(parseList[boardIndex - 1], "+");
9888             break;
9889           case MT_CHECKMATE:
9890           case MT_STAINMATE:
9891             strcat(parseList[boardIndex - 1], "#");
9892             break;
9893         }
9894     }
9895 }
9896
9897
9898 /* Apply a move to the given board  */
9899 void
9900 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9901 {
9902   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9903   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9904
9905     /* [HGM] compute & store e.p. status and castling rights for new position */
9906     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9907
9908       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9909       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9910       board[EP_STATUS] = EP_NONE;
9911       board[EP_FILE] = board[EP_RANK] = 100;
9912
9913   if (fromY == DROP_RANK) {
9914         /* must be first */
9915         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9916             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9917             return;
9918         }
9919         piece = board[toY][toX] = (ChessSquare) fromX;
9920   } else {
9921 //      ChessSquare victim;
9922       int i;
9923
9924       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9925 //           victim = board[killY][killX],
9926            killed = board[killY][killX],
9927            board[killY][killX] = EmptySquare,
9928            board[EP_STATUS] = EP_CAPTURE;
9929
9930       if( board[toY][toX] != EmptySquare ) {
9931            board[EP_STATUS] = EP_CAPTURE;
9932            if( (fromX != toX || fromY != toY) && // not igui!
9933                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9934                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9935                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9936            }
9937       }
9938
9939       pawn = board[fromY][fromX];
9940       if( pawn == WhiteLance || pawn == BlackLance ) {
9941            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9942                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9943                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9944            }
9945       }
9946       if( pawn == WhitePawn ) {
9947            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9948                board[EP_STATUS] = EP_PAWN_MOVE;
9949            if( toY-fromY>=2) {
9950                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9951                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9952                         gameInfo.variant != VariantBerolina || toX < fromX)
9953                       board[EP_STATUS] = toX | berolina;
9954                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9955                         gameInfo.variant != VariantBerolina || toX > fromX)
9956                       board[EP_STATUS] = toX;
9957            }
9958       } else
9959       if( pawn == BlackPawn ) {
9960            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9961                board[EP_STATUS] = EP_PAWN_MOVE;
9962            if( toY-fromY<= -2) {
9963                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9964                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9965                         gameInfo.variant != VariantBerolina || toX < fromX)
9966                       board[EP_STATUS] = toX | berolina;
9967                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9968                         gameInfo.variant != VariantBerolina || toX > fromX)
9969                       board[EP_STATUS] = toX;
9970            }
9971        }
9972
9973        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9974        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9975        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9976        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9977
9978        for(i=0; i<nrCastlingRights; i++) {
9979            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9980               board[CASTLING][i] == toX   && castlingRank[i] == toY
9981              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9982        }
9983
9984        if(gameInfo.variant == VariantSChess) { // update virginity
9985            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9986            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9987            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9988            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9989        }
9990
9991      if (fromX == toX && fromY == toY) return;
9992
9993      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9994      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9995      if(gameInfo.variant == VariantKnightmate)
9996          king += (int) WhiteUnicorn - (int) WhiteKing;
9997
9998     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
9999        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
10000         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10001         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10002         board[EP_STATUS] = EP_NONE; // capture was fake!
10003     } else
10004     /* Code added by Tord: */
10005     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10006     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10007         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10008       board[EP_STATUS] = EP_NONE; // capture was fake!
10009       board[fromY][fromX] = EmptySquare;
10010       board[toY][toX] = EmptySquare;
10011       if((toX > fromX) != (piece == WhiteRook)) {
10012         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10013       } else {
10014         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10015       }
10016     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10017                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10018       board[EP_STATUS] = EP_NONE;
10019       board[fromY][fromX] = EmptySquare;
10020       board[toY][toX] = EmptySquare;
10021       if((toX > fromX) != (piece == BlackRook)) {
10022         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10023       } else {
10024         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10025       }
10026     /* End of code added by Tord */
10027
10028     } else if (board[fromY][fromX] == king
10029         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10030         && toY == fromY && toX > fromX+1) {
10031         board[fromY][fromX] = EmptySquare;
10032         board[toY][toX] = king;
10033         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10034         board[toY][toX-1] = board[fromY][rookX];
10035         board[fromY][rookX] = EmptySquare;
10036     } else if (board[fromY][fromX] == king
10037         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10038                && toY == fromY && toX < fromX-1) {
10039         board[fromY][fromX] = EmptySquare;
10040         board[toY][toX] = king;
10041         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10042         board[toY][toX+1] = board[fromY][rookX];
10043         board[fromY][rookX] = EmptySquare;
10044     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10045                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10046                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10047                ) {
10048         /* white pawn promotion */
10049         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10050         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10051             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10052         board[fromY][fromX] = EmptySquare;
10053     } else if ((fromY >= BOARD_HEIGHT>>1)
10054                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10055                && (toX != fromX)
10056                && gameInfo.variant != VariantXiangqi
10057                && gameInfo.variant != VariantBerolina
10058                && (pawn == WhitePawn)
10059                && (board[toY][toX] == EmptySquare)) {
10060         board[fromY][fromX] = EmptySquare;
10061         board[toY][toX] = piece;
10062         if(toY == epRank - 128 + 1)
10063             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10064         else
10065             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10066     } else if ((fromY == BOARD_HEIGHT-4)
10067                && (toX == fromX)
10068                && gameInfo.variant == VariantBerolina
10069                && (board[fromY][fromX] == WhitePawn)
10070                && (board[toY][toX] == EmptySquare)) {
10071         board[fromY][fromX] = EmptySquare;
10072         board[toY][toX] = WhitePawn;
10073         if(oldEP & EP_BEROLIN_A) {
10074                 captured = board[fromY][fromX-1];
10075                 board[fromY][fromX-1] = EmptySquare;
10076         }else{  captured = board[fromY][fromX+1];
10077                 board[fromY][fromX+1] = EmptySquare;
10078         }
10079     } else if (board[fromY][fromX] == king
10080         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10081                && toY == fromY && toX > fromX+1) {
10082         board[fromY][fromX] = EmptySquare;
10083         board[toY][toX] = king;
10084         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10085         board[toY][toX-1] = board[fromY][rookX];
10086         board[fromY][rookX] = EmptySquare;
10087     } else if (board[fromY][fromX] == king
10088         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10089                && toY == fromY && toX < fromX-1) {
10090         board[fromY][fromX] = EmptySquare;
10091         board[toY][toX] = king;
10092         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10093         board[toY][toX+1] = board[fromY][rookX];
10094         board[fromY][rookX] = EmptySquare;
10095     } else if (fromY == 7 && fromX == 3
10096                && board[fromY][fromX] == BlackKing
10097                && toY == 7 && toX == 5) {
10098         board[fromY][fromX] = EmptySquare;
10099         board[toY][toX] = BlackKing;
10100         board[fromY][7] = EmptySquare;
10101         board[toY][4] = BlackRook;
10102     } else if (fromY == 7 && fromX == 3
10103                && board[fromY][fromX] == BlackKing
10104                && toY == 7 && toX == 1) {
10105         board[fromY][fromX] = EmptySquare;
10106         board[toY][toX] = BlackKing;
10107         board[fromY][0] = EmptySquare;
10108         board[toY][2] = BlackRook;
10109     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10110                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10111                && toY < promoRank && promoChar
10112                ) {
10113         /* black pawn promotion */
10114         board[toY][toX] = CharToPiece(ToLower(promoChar));
10115         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10116             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10117         board[fromY][fromX] = EmptySquare;
10118     } else if ((fromY < BOARD_HEIGHT>>1)
10119                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10120                && (toX != fromX)
10121                && gameInfo.variant != VariantXiangqi
10122                && gameInfo.variant != VariantBerolina
10123                && (pawn == BlackPawn)
10124                && (board[toY][toX] == EmptySquare)) {
10125         board[fromY][fromX] = EmptySquare;
10126         board[toY][toX] = piece;
10127         if(toY == epRank - 128 - 1)
10128             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10129         else
10130             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10131     } else if ((fromY == 3)
10132                && (toX == fromX)
10133                && gameInfo.variant == VariantBerolina
10134                && (board[fromY][fromX] == BlackPawn)
10135                && (board[toY][toX] == EmptySquare)) {
10136         board[fromY][fromX] = EmptySquare;
10137         board[toY][toX] = BlackPawn;
10138         if(oldEP & EP_BEROLIN_A) {
10139                 captured = board[fromY][fromX-1];
10140                 board[fromY][fromX-1] = EmptySquare;
10141         }else{  captured = board[fromY][fromX+1];
10142                 board[fromY][fromX+1] = EmptySquare;
10143         }
10144     } else {
10145         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10146         board[fromY][fromX] = EmptySquare;
10147         board[toY][toX] = piece;
10148     }
10149   }
10150
10151     if (gameInfo.holdingsWidth != 0) {
10152
10153       /* !!A lot more code needs to be written to support holdings  */
10154       /* [HGM] OK, so I have written it. Holdings are stored in the */
10155       /* penultimate board files, so they are automaticlly stored   */
10156       /* in the game history.                                       */
10157       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10158                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10159         /* Delete from holdings, by decreasing count */
10160         /* and erasing image if necessary            */
10161         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10162         if(p < (int) BlackPawn) { /* white drop */
10163              p -= (int)WhitePawn;
10164                  p = PieceToNumber((ChessSquare)p);
10165              if(p >= gameInfo.holdingsSize) p = 0;
10166              if(--board[p][BOARD_WIDTH-2] <= 0)
10167                   board[p][BOARD_WIDTH-1] = EmptySquare;
10168              if((int)board[p][BOARD_WIDTH-2] < 0)
10169                         board[p][BOARD_WIDTH-2] = 0;
10170         } else {                  /* black drop */
10171              p -= (int)BlackPawn;
10172                  p = PieceToNumber((ChessSquare)p);
10173              if(p >= gameInfo.holdingsSize) p = 0;
10174              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10175                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10176              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10177                         board[BOARD_HEIGHT-1-p][1] = 0;
10178         }
10179       }
10180       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10181           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10182         /* [HGM] holdings: Add to holdings, if holdings exist */
10183         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10184                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10185                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10186         }
10187         p = (int) captured;
10188         if (p >= (int) BlackPawn) {
10189           p -= (int)BlackPawn;
10190           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10191                   /* Restore shogi-promoted piece to its original  first */
10192                   captured = (ChessSquare) (DEMOTED captured);
10193                   p = DEMOTED p;
10194           }
10195           p = PieceToNumber((ChessSquare)p);
10196           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10197           board[p][BOARD_WIDTH-2]++;
10198           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10199         } else {
10200           p -= (int)WhitePawn;
10201           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10202                   captured = (ChessSquare) (DEMOTED captured);
10203                   p = DEMOTED p;
10204           }
10205           p = PieceToNumber((ChessSquare)p);
10206           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10207           board[BOARD_HEIGHT-1-p][1]++;
10208           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10209         }
10210       }
10211     } else if (gameInfo.variant == VariantAtomic) {
10212       if (captured != EmptySquare) {
10213         int y, x;
10214         for (y = toY-1; y <= toY+1; y++) {
10215           for (x = toX-1; x <= toX+1; x++) {
10216             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10217                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10218               board[y][x] = EmptySquare;
10219             }
10220           }
10221         }
10222         board[toY][toX] = EmptySquare;
10223       }
10224     }
10225
10226     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10227         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10228     } else
10229     if(promoChar == '+') {
10230         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10231         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10232         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10233           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10234     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10235         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10236         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10237            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10238         board[toY][toX] = newPiece;
10239     }
10240     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10241                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10242         // [HGM] superchess: take promotion piece out of holdings
10243         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10244         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10245             if(!--board[k][BOARD_WIDTH-2])
10246                 board[k][BOARD_WIDTH-1] = EmptySquare;
10247         } else {
10248             if(!--board[BOARD_HEIGHT-1-k][1])
10249                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10250         }
10251     }
10252 }
10253
10254 /* Updates forwardMostMove */
10255 void
10256 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10257 {
10258     int x = toX, y = toY;
10259     char *s = parseList[forwardMostMove];
10260     ChessSquare p = boards[forwardMostMove][toY][toX];
10261 //    forwardMostMove++; // [HGM] bare: moved downstream
10262
10263     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10264     (void) CoordsToAlgebraic(boards[forwardMostMove],
10265                              PosFlags(forwardMostMove),
10266                              fromY, fromX, y, x, promoChar,
10267                              s);
10268     if(killX >= 0 && killY >= 0)
10269         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10270
10271     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10272         int timeLeft; static int lastLoadFlag=0; int king, piece;
10273         piece = boards[forwardMostMove][fromY][fromX];
10274         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10275         if(gameInfo.variant == VariantKnightmate)
10276             king += (int) WhiteUnicorn - (int) WhiteKing;
10277         if(forwardMostMove == 0) {
10278             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10279                 fprintf(serverMoves, "%s;", UserName());
10280             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10281                 fprintf(serverMoves, "%s;", second.tidy);
10282             fprintf(serverMoves, "%s;", first.tidy);
10283             if(gameMode == MachinePlaysWhite)
10284                 fprintf(serverMoves, "%s;", UserName());
10285             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10286                 fprintf(serverMoves, "%s;", second.tidy);
10287         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10288         lastLoadFlag = loadFlag;
10289         // print base move
10290         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10291         // print castling suffix
10292         if( toY == fromY && piece == king ) {
10293             if(toX-fromX > 1)
10294                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10295             if(fromX-toX >1)
10296                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10297         }
10298         // e.p. suffix
10299         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10300              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10301              boards[forwardMostMove][toY][toX] == EmptySquare
10302              && fromX != toX && fromY != toY)
10303                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10304         // promotion suffix
10305         if(promoChar != NULLCHAR) {
10306             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10307                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10308                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10309             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10310         }
10311         if(!loadFlag) {
10312                 char buf[MOVE_LEN*2], *p; int len;
10313             fprintf(serverMoves, "/%d/%d",
10314                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10315             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10316             else                      timeLeft = blackTimeRemaining/1000;
10317             fprintf(serverMoves, "/%d", timeLeft);
10318                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10319                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10320                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10321                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10322             fprintf(serverMoves, "/%s", buf);
10323         }
10324         fflush(serverMoves);
10325     }
10326
10327     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10328         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10329       return;
10330     }
10331     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10332     if (commentList[forwardMostMove+1] != NULL) {
10333         free(commentList[forwardMostMove+1]);
10334         commentList[forwardMostMove+1] = NULL;
10335     }
10336     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10337     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10338     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10339     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10340     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10341     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10342     adjustedClock = FALSE;
10343     gameInfo.result = GameUnfinished;
10344     if (gameInfo.resultDetails != NULL) {
10345         free(gameInfo.resultDetails);
10346         gameInfo.resultDetails = NULL;
10347     }
10348     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10349                               moveList[forwardMostMove - 1]);
10350     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10351       case MT_NONE:
10352       case MT_STALEMATE:
10353       default:
10354         break;
10355       case MT_CHECK:
10356         if(!IS_SHOGI(gameInfo.variant))
10357             strcat(parseList[forwardMostMove - 1], "+");
10358         break;
10359       case MT_CHECKMATE:
10360       case MT_STAINMATE:
10361         strcat(parseList[forwardMostMove - 1], "#");
10362         break;
10363     }
10364 }
10365
10366 /* Updates currentMove if not pausing */
10367 void
10368 ShowMove (int fromX, int fromY, int toX, int toY)
10369 {
10370     int instant = (gameMode == PlayFromGameFile) ?
10371         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10372     if(appData.noGUI) return;
10373     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10374         if (!instant) {
10375             if (forwardMostMove == currentMove + 1) {
10376                 AnimateMove(boards[forwardMostMove - 1],
10377                             fromX, fromY, toX, toY);
10378             }
10379         }
10380         currentMove = forwardMostMove;
10381     }
10382
10383     killX = killY = -1; // [HGM] lion: used up
10384
10385     if (instant) return;
10386
10387     DisplayMove(currentMove - 1);
10388     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10389             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10390                 SetHighlights(fromX, fromY, toX, toY);
10391             }
10392     }
10393     DrawPosition(FALSE, boards[currentMove]);
10394     DisplayBothClocks();
10395     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10396 }
10397
10398 void
10399 SendEgtPath (ChessProgramState *cps)
10400 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10401         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10402
10403         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10404
10405         while(*p) {
10406             char c, *q = name+1, *r, *s;
10407
10408             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10409             while(*p && *p != ',') *q++ = *p++;
10410             *q++ = ':'; *q = 0;
10411             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10412                 strcmp(name, ",nalimov:") == 0 ) {
10413                 // take nalimov path from the menu-changeable option first, if it is defined
10414               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10415                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10416             } else
10417             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10418                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10419                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10420                 s = r = StrStr(s, ":") + 1; // beginning of path info
10421                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10422                 c = *r; *r = 0;             // temporarily null-terminate path info
10423                     *--q = 0;               // strip of trailig ':' from name
10424                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10425                 *r = c;
10426                 SendToProgram(buf,cps);     // send egtbpath command for this format
10427             }
10428             if(*p == ',') p++; // read away comma to position for next format name
10429         }
10430 }
10431
10432 static int
10433 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10434 {
10435       int width = 8, height = 8, holdings = 0;             // most common sizes
10436       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10437       // correct the deviations default for each variant
10438       if( v == VariantXiangqi ) width = 9,  height = 10;
10439       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10440       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10441       if( v == VariantCapablanca || v == VariantCapaRandom ||
10442           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10443                                 width = 10;
10444       if( v == VariantCourier ) width = 12;
10445       if( v == VariantSuper )                            holdings = 8;
10446       if( v == VariantGreat )   width = 10,              holdings = 8;
10447       if( v == VariantSChess )                           holdings = 7;
10448       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10449       if( v == VariantChuChess) width = 10, height = 10;
10450       if( v == VariantChu )     width = 12, height = 12;
10451       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10452              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10453              holdingsSize >= 0 && holdingsSize != holdings;
10454 }
10455
10456 char variantError[MSG_SIZ];
10457
10458 char *
10459 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10460 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10461       char *p, *variant = VariantName(v);
10462       static char b[MSG_SIZ];
10463       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10464            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10465                                                holdingsSize, variant); // cook up sized variant name
10466            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10467            if(StrStr(list, b) == NULL) {
10468                // specific sized variant not known, check if general sizing allowed
10469                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10470                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10471                             boardWidth, boardHeight, holdingsSize, engine);
10472                    return NULL;
10473                }
10474                /* [HGM] here we really should compare with the maximum supported board size */
10475            }
10476       } else snprintf(b, MSG_SIZ,"%s", variant);
10477       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10478       p = StrStr(list, b);
10479       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10480       if(p == NULL) {
10481           // occurs not at all in list, or only as sub-string
10482           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10483           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10484               int l = strlen(variantError);
10485               char *q;
10486               while(p != list && p[-1] != ',') p--;
10487               q = strchr(p, ',');
10488               if(q) *q = NULLCHAR;
10489               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10490               if(q) *q= ',';
10491           }
10492           return NULL;
10493       }
10494       return b;
10495 }
10496
10497 void
10498 InitChessProgram (ChessProgramState *cps, int setup)
10499 /* setup needed to setup FRC opening position */
10500 {
10501     char buf[MSG_SIZ], *b;
10502     if (appData.noChessProgram) return;
10503     hintRequested = FALSE;
10504     bookRequested = FALSE;
10505
10506     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10507     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10508     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10509     if(cps->memSize) { /* [HGM] memory */
10510       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10511         SendToProgram(buf, cps);
10512     }
10513     SendEgtPath(cps); /* [HGM] EGT */
10514     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10515       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10516         SendToProgram(buf, cps);
10517     }
10518
10519     setboardSpoiledMachineBlack = FALSE;
10520     SendToProgram(cps->initString, cps);
10521     if (gameInfo.variant != VariantNormal &&
10522         gameInfo.variant != VariantLoadable
10523         /* [HGM] also send variant if board size non-standard */
10524         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10525
10526       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10527                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10528       if (b == NULL) {
10529         VariantClass v;
10530         char c, *q = cps->variants, *p = strchr(q, ',');
10531         if(p) *p = NULLCHAR;
10532         v = StringToVariant(q);
10533         DisplayError(variantError, 0);
10534         if(v != VariantUnknown && cps == &first) {
10535             int w, h, s;
10536             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10537                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10538             ASSIGN(appData.variant, q);
10539             Reset(TRUE, FALSE);
10540         }
10541         if(p) *p = ',';
10542         return;
10543       }
10544
10545       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10546       SendToProgram(buf, cps);
10547     }
10548     currentlyInitializedVariant = gameInfo.variant;
10549
10550     /* [HGM] send opening position in FRC to first engine */
10551     if(setup) {
10552           SendToProgram("force\n", cps);
10553           SendBoard(cps, 0);
10554           /* engine is now in force mode! Set flag to wake it up after first move. */
10555           setboardSpoiledMachineBlack = 1;
10556     }
10557
10558     if (cps->sendICS) {
10559       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10560       SendToProgram(buf, cps);
10561     }
10562     cps->maybeThinking = FALSE;
10563     cps->offeredDraw = 0;
10564     if (!appData.icsActive) {
10565         SendTimeControl(cps, movesPerSession, timeControl,
10566                         timeIncrement, appData.searchDepth,
10567                         searchTime);
10568     }
10569     if (appData.showThinking
10570         // [HGM] thinking: four options require thinking output to be sent
10571         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10572                                 ) {
10573         SendToProgram("post\n", cps);
10574     }
10575     SendToProgram("hard\n", cps);
10576     if (!appData.ponderNextMove) {
10577         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10578            it without being sure what state we are in first.  "hard"
10579            is not a toggle, so that one is OK.
10580          */
10581         SendToProgram("easy\n", cps);
10582     }
10583     if (cps->usePing) {
10584       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10585       SendToProgram(buf, cps);
10586     }
10587     cps->initDone = TRUE;
10588     ClearEngineOutputPane(cps == &second);
10589 }
10590
10591
10592 void
10593 ResendOptions (ChessProgramState *cps)
10594 { // send the stored value of the options
10595   int i;
10596   char buf[MSG_SIZ];
10597   Option *opt = cps->option;
10598   for(i=0; i<cps->nrOptions; i++, opt++) {
10599       switch(opt->type) {
10600         case Spin:
10601         case Slider:
10602         case CheckBox:
10603             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10604           break;
10605         case ComboBox:
10606           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10607           break;
10608         default:
10609             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10610           break;
10611         case Button:
10612         case SaveButton:
10613           continue;
10614       }
10615       SendToProgram(buf, cps);
10616   }
10617 }
10618
10619 void
10620 StartChessProgram (ChessProgramState *cps)
10621 {
10622     char buf[MSG_SIZ];
10623     int err;
10624
10625     if (appData.noChessProgram) return;
10626     cps->initDone = FALSE;
10627
10628     if (strcmp(cps->host, "localhost") == 0) {
10629         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10630     } else if (*appData.remoteShell == NULLCHAR) {
10631         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10632     } else {
10633         if (*appData.remoteUser == NULLCHAR) {
10634           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10635                     cps->program);
10636         } else {
10637           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10638                     cps->host, appData.remoteUser, cps->program);
10639         }
10640         err = StartChildProcess(buf, "", &cps->pr);
10641     }
10642
10643     if (err != 0) {
10644       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10645         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10646         if(cps != &first) return;
10647         appData.noChessProgram = TRUE;
10648         ThawUI();
10649         SetNCPMode();
10650 //      DisplayFatalError(buf, err, 1);
10651 //      cps->pr = NoProc;
10652 //      cps->isr = NULL;
10653         return;
10654     }
10655
10656     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10657     if (cps->protocolVersion > 1) {
10658       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10659       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10660         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10661         cps->comboCnt = 0;  //                and values of combo boxes
10662       }
10663       SendToProgram(buf, cps);
10664       if(cps->reload) ResendOptions(cps);
10665     } else {
10666       SendToProgram("xboard\n", cps);
10667     }
10668 }
10669
10670 void
10671 TwoMachinesEventIfReady P((void))
10672 {
10673   static int curMess = 0;
10674   if (first.lastPing != first.lastPong) {
10675     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10676     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10677     return;
10678   }
10679   if (second.lastPing != second.lastPong) {
10680     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10682     return;
10683   }
10684   DisplayMessage("", ""); curMess = 0;
10685   TwoMachinesEvent();
10686 }
10687
10688 char *
10689 MakeName (char *template)
10690 {
10691     time_t clock;
10692     struct tm *tm;
10693     static char buf[MSG_SIZ];
10694     char *p = buf;
10695     int i;
10696
10697     clock = time((time_t *)NULL);
10698     tm = localtime(&clock);
10699
10700     while(*p++ = *template++) if(p[-1] == '%') {
10701         switch(*template++) {
10702           case 0:   *p = 0; return buf;
10703           case 'Y': i = tm->tm_year+1900; break;
10704           case 'y': i = tm->tm_year-100; break;
10705           case 'M': i = tm->tm_mon+1; break;
10706           case 'd': i = tm->tm_mday; break;
10707           case 'h': i = tm->tm_hour; break;
10708           case 'm': i = tm->tm_min; break;
10709           case 's': i = tm->tm_sec; break;
10710           default:  i = 0;
10711         }
10712         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10713     }
10714     return buf;
10715 }
10716
10717 int
10718 CountPlayers (char *p)
10719 {
10720     int n = 0;
10721     while(p = strchr(p, '\n')) p++, n++; // count participants
10722     return n;
10723 }
10724
10725 FILE *
10726 WriteTourneyFile (char *results, FILE *f)
10727 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10728     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10729     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10730         // create a file with tournament description
10731         fprintf(f, "-participants {%s}\n", appData.participants);
10732         fprintf(f, "-seedBase %d\n", appData.seedBase);
10733         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10734         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10735         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10736         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10737         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10738         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10739         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10740         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10741         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10742         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10743         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10744         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10745         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10746         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10747         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10748         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10749         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10750         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10751         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10752         fprintf(f, "-smpCores %d\n", appData.smpCores);
10753         if(searchTime > 0)
10754                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10755         else {
10756                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10757                 fprintf(f, "-tc %s\n", appData.timeControl);
10758                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10759         }
10760         fprintf(f, "-results \"%s\"\n", results);
10761     }
10762     return f;
10763 }
10764
10765 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10766
10767 void
10768 Substitute (char *participants, int expunge)
10769 {
10770     int i, changed, changes=0, nPlayers=0;
10771     char *p, *q, *r, buf[MSG_SIZ];
10772     if(participants == NULL) return;
10773     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10774     r = p = participants; q = appData.participants;
10775     while(*p && *p == *q) {
10776         if(*p == '\n') r = p+1, nPlayers++;
10777         p++; q++;
10778     }
10779     if(*p) { // difference
10780         while(*p && *p++ != '\n');
10781         while(*q && *q++ != '\n');
10782       changed = nPlayers;
10783         changes = 1 + (strcmp(p, q) != 0);
10784     }
10785     if(changes == 1) { // a single engine mnemonic was changed
10786         q = r; while(*q) nPlayers += (*q++ == '\n');
10787         p = buf; while(*r && (*p = *r++) != '\n') p++;
10788         *p = NULLCHAR;
10789         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10790         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10791         if(mnemonic[i]) { // The substitute is valid
10792             FILE *f;
10793             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10794                 flock(fileno(f), LOCK_EX);
10795                 ParseArgsFromFile(f);
10796                 fseek(f, 0, SEEK_SET);
10797                 FREE(appData.participants); appData.participants = participants;
10798                 if(expunge) { // erase results of replaced engine
10799                     int len = strlen(appData.results), w, b, dummy;
10800                     for(i=0; i<len; i++) {
10801                         Pairing(i, nPlayers, &w, &b, &dummy);
10802                         if((w == changed || b == changed) && appData.results[i] == '*') {
10803                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10804                             fclose(f);
10805                             return;
10806                         }
10807                     }
10808                     for(i=0; i<len; i++) {
10809                         Pairing(i, nPlayers, &w, &b, &dummy);
10810                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10811                     }
10812                 }
10813                 WriteTourneyFile(appData.results, f);
10814                 fclose(f); // release lock
10815                 return;
10816             }
10817         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10818     }
10819     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10820     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10821     free(participants);
10822     return;
10823 }
10824
10825 int
10826 CheckPlayers (char *participants)
10827 {
10828         int i;
10829         char buf[MSG_SIZ], *p;
10830         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10831         while(p = strchr(participants, '\n')) {
10832             *p = NULLCHAR;
10833             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10834             if(!mnemonic[i]) {
10835                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10836                 *p = '\n';
10837                 DisplayError(buf, 0);
10838                 return 1;
10839             }
10840             *p = '\n';
10841             participants = p + 1;
10842         }
10843         return 0;
10844 }
10845
10846 int
10847 CreateTourney (char *name)
10848 {
10849         FILE *f;
10850         if(matchMode && strcmp(name, appData.tourneyFile)) {
10851              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10852         }
10853         if(name[0] == NULLCHAR) {
10854             if(appData.participants[0])
10855                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10856             return 0;
10857         }
10858         f = fopen(name, "r");
10859         if(f) { // file exists
10860             ASSIGN(appData.tourneyFile, name);
10861             ParseArgsFromFile(f); // parse it
10862         } else {
10863             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10864             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10865                 DisplayError(_("Not enough participants"), 0);
10866                 return 0;
10867             }
10868             if(CheckPlayers(appData.participants)) return 0;
10869             ASSIGN(appData.tourneyFile, name);
10870             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10871             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10872         }
10873         fclose(f);
10874         appData.noChessProgram = FALSE;
10875         appData.clockMode = TRUE;
10876         SetGNUMode();
10877         return 1;
10878 }
10879
10880 int
10881 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10882 {
10883     char buf[MSG_SIZ], *p, *q;
10884     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10885     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10886     skip = !all && group[0]; // if group requested, we start in skip mode
10887     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10888         p = names; q = buf; header = 0;
10889         while(*p && *p != '\n') *q++ = *p++;
10890         *q = 0;
10891         if(*p == '\n') p++;
10892         if(buf[0] == '#') {
10893             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10894             depth++; // we must be entering a new group
10895             if(all) continue; // suppress printing group headers when complete list requested
10896             header = 1;
10897             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10898         }
10899         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10900         if(engineList[i]) free(engineList[i]);
10901         engineList[i] = strdup(buf);
10902         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10903         if(engineMnemonic[i]) free(engineMnemonic[i]);
10904         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10905             strcat(buf, " (");
10906             sscanf(q + 8, "%s", buf + strlen(buf));
10907             strcat(buf, ")");
10908         }
10909         engineMnemonic[i] = strdup(buf);
10910         i++;
10911     }
10912     engineList[i] = engineMnemonic[i] = NULL;
10913     return i;
10914 }
10915
10916 // following implemented as macro to avoid type limitations
10917 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10918
10919 void
10920 SwapEngines (int n)
10921 {   // swap settings for first engine and other engine (so far only some selected options)
10922     int h;
10923     char *p;
10924     if(n == 0) return;
10925     SWAP(directory, p)
10926     SWAP(chessProgram, p)
10927     SWAP(isUCI, h)
10928     SWAP(hasOwnBookUCI, h)
10929     SWAP(protocolVersion, h)
10930     SWAP(reuse, h)
10931     SWAP(scoreIsAbsolute, h)
10932     SWAP(timeOdds, h)
10933     SWAP(logo, p)
10934     SWAP(pgnName, p)
10935     SWAP(pvSAN, h)
10936     SWAP(engOptions, p)
10937     SWAP(engInitString, p)
10938     SWAP(computerString, p)
10939     SWAP(features, p)
10940     SWAP(fenOverride, p)
10941     SWAP(NPS, h)
10942     SWAP(accumulateTC, h)
10943     SWAP(drawDepth, h)
10944     SWAP(host, p)
10945     SWAP(pseudo, h)
10946 }
10947
10948 int
10949 GetEngineLine (char *s, int n)
10950 {
10951     int i;
10952     char buf[MSG_SIZ];
10953     extern char *icsNames;
10954     if(!s || !*s) return 0;
10955     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10956     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10957     if(!mnemonic[i]) return 0;
10958     if(n == 11) return 1; // just testing if there was a match
10959     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10960     if(n == 1) SwapEngines(n);
10961     ParseArgsFromString(buf);
10962     if(n == 1) SwapEngines(n);
10963     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10964         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10965         ParseArgsFromString(buf);
10966     }
10967     return 1;
10968 }
10969
10970 int
10971 SetPlayer (int player, char *p)
10972 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10973     int i;
10974     char buf[MSG_SIZ], *engineName;
10975     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10976     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10977     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10978     if(mnemonic[i]) {
10979         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10980         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10981         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10982         ParseArgsFromString(buf);
10983     } else { // no engine with this nickname is installed!
10984         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10985         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10986         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10987         ModeHighlight();
10988         DisplayError(buf, 0);
10989         return 0;
10990     }
10991     free(engineName);
10992     return i;
10993 }
10994
10995 char *recentEngines;
10996
10997 void
10998 RecentEngineEvent (int nr)
10999 {
11000     int n;
11001 //    SwapEngines(1); // bump first to second
11002 //    ReplaceEngine(&second, 1); // and load it there
11003     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11004     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11005     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11006         ReplaceEngine(&first, 0);
11007         FloatToFront(&appData.recentEngineList, command[n]);
11008     }
11009 }
11010
11011 int
11012 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11013 {   // determine players from game number
11014     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11015
11016     if(appData.tourneyType == 0) {
11017         roundsPerCycle = (nPlayers - 1) | 1;
11018         pairingsPerRound = nPlayers / 2;
11019     } else if(appData.tourneyType > 0) {
11020         roundsPerCycle = nPlayers - appData.tourneyType;
11021         pairingsPerRound = appData.tourneyType;
11022     }
11023     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11024     gamesPerCycle = gamesPerRound * roundsPerCycle;
11025     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11026     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11027     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11028     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11029     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11030     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11031
11032     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11033     if(appData.roundSync) *syncInterval = gamesPerRound;
11034
11035     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11036
11037     if(appData.tourneyType == 0) {
11038         if(curPairing == (nPlayers-1)/2 ) {
11039             *whitePlayer = curRound;
11040             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11041         } else {
11042             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11043             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11044             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11045             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11046         }
11047     } else if(appData.tourneyType > 1) {
11048         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11049         *whitePlayer = curRound + appData.tourneyType;
11050     } else if(appData.tourneyType > 0) {
11051         *whitePlayer = curPairing;
11052         *blackPlayer = curRound + appData.tourneyType;
11053     }
11054
11055     // take care of white/black alternation per round.
11056     // For cycles and games this is already taken care of by default, derived from matchGame!
11057     return curRound & 1;
11058 }
11059
11060 int
11061 NextTourneyGame (int nr, int *swapColors)
11062 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11063     char *p, *q;
11064     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11065     FILE *tf;
11066     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11067     tf = fopen(appData.tourneyFile, "r");
11068     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11069     ParseArgsFromFile(tf); fclose(tf);
11070     InitTimeControls(); // TC might be altered from tourney file
11071
11072     nPlayers = CountPlayers(appData.participants); // count participants
11073     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11074     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11075
11076     if(syncInterval) {
11077         p = q = appData.results;
11078         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11079         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11080             DisplayMessage(_("Waiting for other game(s)"),"");
11081             waitingForGame = TRUE;
11082             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11083             return 0;
11084         }
11085         waitingForGame = FALSE;
11086     }
11087
11088     if(appData.tourneyType < 0) {
11089         if(nr>=0 && !pairingReceived) {
11090             char buf[1<<16];
11091             if(pairing.pr == NoProc) {
11092                 if(!appData.pairingEngine[0]) {
11093                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11094                     return 0;
11095                 }
11096                 StartChessProgram(&pairing); // starts the pairing engine
11097             }
11098             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11099             SendToProgram(buf, &pairing);
11100             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11101             SendToProgram(buf, &pairing);
11102             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11103         }
11104         pairingReceived = 0;                              // ... so we continue here
11105         *swapColors = 0;
11106         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11107         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11108         matchGame = 1; roundNr = nr / syncInterval + 1;
11109     }
11110
11111     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11112
11113     // redefine engines, engine dir, etc.
11114     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11115     if(first.pr == NoProc) {
11116       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11117       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11118     }
11119     if(second.pr == NoProc) {
11120       SwapEngines(1);
11121       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11122       SwapEngines(1);         // and make that valid for second engine by swapping
11123       InitEngine(&second, 1);
11124     }
11125     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11126     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11127     return OK;
11128 }
11129
11130 void
11131 NextMatchGame ()
11132 {   // performs game initialization that does not invoke engines, and then tries to start the game
11133     int res, firstWhite, swapColors = 0;
11134     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11135     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
11136         char buf[MSG_SIZ];
11137         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11138         if(strcmp(buf, currentDebugFile)) { // name has changed
11139             FILE *f = fopen(buf, "w");
11140             if(f) { // if opening the new file failed, just keep using the old one
11141                 ASSIGN(currentDebugFile, buf);
11142                 fclose(debugFP);
11143                 debugFP = f;
11144             }
11145             if(appData.serverFileName) {
11146                 if(serverFP) fclose(serverFP);
11147                 serverFP = fopen(appData.serverFileName, "w");
11148                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11149                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11150             }
11151         }
11152     }
11153     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11154     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11155     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11156     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11157     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11158     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11159     Reset(FALSE, first.pr != NoProc);
11160     res = LoadGameOrPosition(matchGame); // setup game
11161     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11162     if(!res) return; // abort when bad game/pos file
11163     TwoMachinesEvent();
11164 }
11165
11166 void
11167 UserAdjudicationEvent (int result)
11168 {
11169     ChessMove gameResult = GameIsDrawn;
11170
11171     if( result > 0 ) {
11172         gameResult = WhiteWins;
11173     }
11174     else if( result < 0 ) {
11175         gameResult = BlackWins;
11176     }
11177
11178     if( gameMode == TwoMachinesPlay ) {
11179         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11180     }
11181 }
11182
11183
11184 // [HGM] save: calculate checksum of game to make games easily identifiable
11185 int
11186 StringCheckSum (char *s)
11187 {
11188         int i = 0;
11189         if(s==NULL) return 0;
11190         while(*s) i = i*259 + *s++;
11191         return i;
11192 }
11193
11194 int
11195 GameCheckSum ()
11196 {
11197         int i, sum=0;
11198         for(i=backwardMostMove; i<forwardMostMove; i++) {
11199                 sum += pvInfoList[i].depth;
11200                 sum += StringCheckSum(parseList[i]);
11201                 sum += StringCheckSum(commentList[i]);
11202                 sum *= 261;
11203         }
11204         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11205         return sum + StringCheckSum(commentList[i]);
11206 } // end of save patch
11207
11208 void
11209 GameEnds (ChessMove result, char *resultDetails, int whosays)
11210 {
11211     GameMode nextGameMode;
11212     int isIcsGame;
11213     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11214
11215     if(endingGame) return; /* [HGM] crash: forbid recursion */
11216     endingGame = 1;
11217     if(twoBoards) { // [HGM] dual: switch back to one board
11218         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11219         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11220     }
11221     if (appData.debugMode) {
11222       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11223               result, resultDetails ? resultDetails : "(null)", whosays);
11224     }
11225
11226     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11227
11228     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11229
11230     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11231         /* If we are playing on ICS, the server decides when the
11232            game is over, but the engine can offer to draw, claim
11233            a draw, or resign.
11234          */
11235 #if ZIPPY
11236         if (appData.zippyPlay && first.initDone) {
11237             if (result == GameIsDrawn) {
11238                 /* In case draw still needs to be claimed */
11239                 SendToICS(ics_prefix);
11240                 SendToICS("draw\n");
11241             } else if (StrCaseStr(resultDetails, "resign")) {
11242                 SendToICS(ics_prefix);
11243                 SendToICS("resign\n");
11244             }
11245         }
11246 #endif
11247         endingGame = 0; /* [HGM] crash */
11248         return;
11249     }
11250
11251     /* If we're loading the game from a file, stop */
11252     if (whosays == GE_FILE) {
11253       (void) StopLoadGameTimer();
11254       gameFileFP = NULL;
11255     }
11256
11257     /* Cancel draw offers */
11258     first.offeredDraw = second.offeredDraw = 0;
11259
11260     /* If this is an ICS game, only ICS can really say it's done;
11261        if not, anyone can. */
11262     isIcsGame = (gameMode == IcsPlayingWhite ||
11263                  gameMode == IcsPlayingBlack ||
11264                  gameMode == IcsObserving    ||
11265                  gameMode == IcsExamining);
11266
11267     if (!isIcsGame || whosays == GE_ICS) {
11268         /* OK -- not an ICS game, or ICS said it was done */
11269         StopClocks();
11270         if (!isIcsGame && !appData.noChessProgram)
11271           SetUserThinkingEnables();
11272
11273         /* [HGM] if a machine claims the game end we verify this claim */
11274         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11275             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11276                 char claimer;
11277                 ChessMove trueResult = (ChessMove) -1;
11278
11279                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11280                                             first.twoMachinesColor[0] :
11281                                             second.twoMachinesColor[0] ;
11282
11283                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11284                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11285                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11286                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11287                 } else
11288                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11289                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11290                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11291                 } else
11292                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11293                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11294                 }
11295
11296                 // now verify win claims, but not in drop games, as we don't understand those yet
11297                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11298                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11299                     (result == WhiteWins && claimer == 'w' ||
11300                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11301                       if (appData.debugMode) {
11302                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11303                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11304                       }
11305                       if(result != trueResult) {
11306                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11307                               result = claimer == 'w' ? BlackWins : WhiteWins;
11308                               resultDetails = buf;
11309                       }
11310                 } else
11311                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11312                     && (forwardMostMove <= backwardMostMove ||
11313                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11314                         (claimer=='b')==(forwardMostMove&1))
11315                                                                                   ) {
11316                       /* [HGM] verify: draws that were not flagged are false claims */
11317                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11318                       result = claimer == 'w' ? BlackWins : WhiteWins;
11319                       resultDetails = buf;
11320                 }
11321                 /* (Claiming a loss is accepted no questions asked!) */
11322             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11323                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11324                 result = GameUnfinished;
11325                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11326             }
11327             /* [HGM] bare: don't allow bare King to win */
11328             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11329                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11330                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11331                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11332                && result != GameIsDrawn)
11333             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11334                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11335                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11336                         if(p >= 0 && p <= (int)WhiteKing) k++;
11337                 }
11338                 if (appData.debugMode) {
11339                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11340                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11341                 }
11342                 if(k <= 1) {
11343                         result = GameIsDrawn;
11344                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11345                         resultDetails = buf;
11346                 }
11347             }
11348         }
11349
11350
11351         if(serverMoves != NULL && !loadFlag) { char c = '=';
11352             if(result==WhiteWins) c = '+';
11353             if(result==BlackWins) c = '-';
11354             if(resultDetails != NULL)
11355                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11356         }
11357         if (resultDetails != NULL) {
11358             gameInfo.result = result;
11359             gameInfo.resultDetails = StrSave(resultDetails);
11360
11361             /* display last move only if game was not loaded from file */
11362             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11363                 DisplayMove(currentMove - 1);
11364
11365             if (forwardMostMove != 0) {
11366                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11367                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11368                                                                 ) {
11369                     if (*appData.saveGameFile != NULLCHAR) {
11370                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11371                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11372                         else
11373                         SaveGameToFile(appData.saveGameFile, TRUE);
11374                     } else if (appData.autoSaveGames) {
11375                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11376                     }
11377                     if (*appData.savePositionFile != NULLCHAR) {
11378                         SavePositionToFile(appData.savePositionFile);
11379                     }
11380                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11381                 }
11382             }
11383
11384             /* Tell program how game ended in case it is learning */
11385             /* [HGM] Moved this to after saving the PGN, just in case */
11386             /* engine died and we got here through time loss. In that */
11387             /* case we will get a fatal error writing the pipe, which */
11388             /* would otherwise lose us the PGN.                       */
11389             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11390             /* output during GameEnds should never be fatal anymore   */
11391             if (gameMode == MachinePlaysWhite ||
11392                 gameMode == MachinePlaysBlack ||
11393                 gameMode == TwoMachinesPlay ||
11394                 gameMode == IcsPlayingWhite ||
11395                 gameMode == IcsPlayingBlack ||
11396                 gameMode == BeginningOfGame) {
11397                 char buf[MSG_SIZ];
11398                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11399                         resultDetails);
11400                 if (first.pr != NoProc) {
11401                     SendToProgram(buf, &first);
11402                 }
11403                 if (second.pr != NoProc &&
11404                     gameMode == TwoMachinesPlay) {
11405                     SendToProgram(buf, &second);
11406                 }
11407             }
11408         }
11409
11410         if (appData.icsActive) {
11411             if (appData.quietPlay &&
11412                 (gameMode == IcsPlayingWhite ||
11413                  gameMode == IcsPlayingBlack)) {
11414                 SendToICS(ics_prefix);
11415                 SendToICS("set shout 1\n");
11416             }
11417             nextGameMode = IcsIdle;
11418             ics_user_moved = FALSE;
11419             /* clean up premove.  It's ugly when the game has ended and the
11420              * premove highlights are still on the board.
11421              */
11422             if (gotPremove) {
11423               gotPremove = FALSE;
11424               ClearPremoveHighlights();
11425               DrawPosition(FALSE, boards[currentMove]);
11426             }
11427             if (whosays == GE_ICS) {
11428                 switch (result) {
11429                 case WhiteWins:
11430                     if (gameMode == IcsPlayingWhite)
11431                         PlayIcsWinSound();
11432                     else if(gameMode == IcsPlayingBlack)
11433                         PlayIcsLossSound();
11434                     break;
11435                 case BlackWins:
11436                     if (gameMode == IcsPlayingBlack)
11437                         PlayIcsWinSound();
11438                     else if(gameMode == IcsPlayingWhite)
11439                         PlayIcsLossSound();
11440                     break;
11441                 case GameIsDrawn:
11442                     PlayIcsDrawSound();
11443                     break;
11444                 default:
11445                     PlayIcsUnfinishedSound();
11446                 }
11447             }
11448             if(appData.quitNext) { ExitEvent(0); return; }
11449         } else if (gameMode == EditGame ||
11450                    gameMode == PlayFromGameFile ||
11451                    gameMode == AnalyzeMode ||
11452                    gameMode == AnalyzeFile) {
11453             nextGameMode = gameMode;
11454         } else {
11455             nextGameMode = EndOfGame;
11456         }
11457         pausing = FALSE;
11458         ModeHighlight();
11459     } else {
11460         nextGameMode = gameMode;
11461     }
11462
11463     if (appData.noChessProgram) {
11464         gameMode = nextGameMode;
11465         ModeHighlight();
11466         endingGame = 0; /* [HGM] crash */
11467         return;
11468     }
11469
11470     if (first.reuse) {
11471         /* Put first chess program into idle state */
11472         if (first.pr != NoProc &&
11473             (gameMode == MachinePlaysWhite ||
11474              gameMode == MachinePlaysBlack ||
11475              gameMode == TwoMachinesPlay ||
11476              gameMode == IcsPlayingWhite ||
11477              gameMode == IcsPlayingBlack ||
11478              gameMode == BeginningOfGame)) {
11479             SendToProgram("force\n", &first);
11480             if (first.usePing) {
11481               char buf[MSG_SIZ];
11482               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11483               SendToProgram(buf, &first);
11484             }
11485         }
11486     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11487         /* Kill off first chess program */
11488         if (first.isr != NULL)
11489           RemoveInputSource(first.isr);
11490         first.isr = NULL;
11491
11492         if (first.pr != NoProc) {
11493             ExitAnalyzeMode();
11494             DoSleep( appData.delayBeforeQuit );
11495             SendToProgram("quit\n", &first);
11496             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11497             first.reload = TRUE;
11498         }
11499         first.pr = NoProc;
11500     }
11501     if (second.reuse) {
11502         /* Put second chess program into idle state */
11503         if (second.pr != NoProc &&
11504             gameMode == TwoMachinesPlay) {
11505             SendToProgram("force\n", &second);
11506             if (second.usePing) {
11507               char buf[MSG_SIZ];
11508               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11509               SendToProgram(buf, &second);
11510             }
11511         }
11512     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11513         /* Kill off second chess program */
11514         if (second.isr != NULL)
11515           RemoveInputSource(second.isr);
11516         second.isr = NULL;
11517
11518         if (second.pr != NoProc) {
11519             DoSleep( appData.delayBeforeQuit );
11520             SendToProgram("quit\n", &second);
11521             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11522             second.reload = TRUE;
11523         }
11524         second.pr = NoProc;
11525     }
11526
11527     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11528         char resChar = '=';
11529         switch (result) {
11530         case WhiteWins:
11531           resChar = '+';
11532           if (first.twoMachinesColor[0] == 'w') {
11533             first.matchWins++;
11534           } else {
11535             second.matchWins++;
11536           }
11537           break;
11538         case BlackWins:
11539           resChar = '-';
11540           if (first.twoMachinesColor[0] == 'b') {
11541             first.matchWins++;
11542           } else {
11543             second.matchWins++;
11544           }
11545           break;
11546         case GameUnfinished:
11547           resChar = ' ';
11548         default:
11549           break;
11550         }
11551
11552         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11553         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11554             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11555             ReserveGame(nextGame, resChar); // sets nextGame
11556             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11557             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11558         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11559
11560         if (nextGame <= appData.matchGames && !abortMatch) {
11561             gameMode = nextGameMode;
11562             matchGame = nextGame; // this will be overruled in tourney mode!
11563             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11564             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11565             endingGame = 0; /* [HGM] crash */
11566             return;
11567         } else {
11568             gameMode = nextGameMode;
11569             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11570                      first.tidy, second.tidy,
11571                      first.matchWins, second.matchWins,
11572                      appData.matchGames - (first.matchWins + second.matchWins));
11573             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11574             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11575             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11576             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11577                 first.twoMachinesColor = "black\n";
11578                 second.twoMachinesColor = "white\n";
11579             } else {
11580                 first.twoMachinesColor = "white\n";
11581                 second.twoMachinesColor = "black\n";
11582             }
11583         }
11584     }
11585     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11586         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11587       ExitAnalyzeMode();
11588     gameMode = nextGameMode;
11589     ModeHighlight();
11590     endingGame = 0;  /* [HGM] crash */
11591     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11592         if(matchMode == TRUE) { // match through command line: exit with or without popup
11593             if(ranking) {
11594                 ToNrEvent(forwardMostMove);
11595                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11596                 else ExitEvent(0);
11597             } else DisplayFatalError(buf, 0, 0);
11598         } else { // match through menu; just stop, with or without popup
11599             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11600             ModeHighlight();
11601             if(ranking){
11602                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11603             } else DisplayNote(buf);
11604       }
11605       if(ranking) free(ranking);
11606     }
11607 }
11608
11609 /* Assumes program was just initialized (initString sent).
11610    Leaves program in force mode. */
11611 void
11612 FeedMovesToProgram (ChessProgramState *cps, int upto)
11613 {
11614     int i;
11615
11616     if (appData.debugMode)
11617       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11618               startedFromSetupPosition ? "position and " : "",
11619               backwardMostMove, upto, cps->which);
11620     if(currentlyInitializedVariant != gameInfo.variant) {
11621       char buf[MSG_SIZ];
11622         // [HGM] variantswitch: make engine aware of new variant
11623         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11624                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11625                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11626         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11627         SendToProgram(buf, cps);
11628         currentlyInitializedVariant = gameInfo.variant;
11629     }
11630     SendToProgram("force\n", cps);
11631     if (startedFromSetupPosition) {
11632         SendBoard(cps, backwardMostMove);
11633     if (appData.debugMode) {
11634         fprintf(debugFP, "feedMoves\n");
11635     }
11636     }
11637     for (i = backwardMostMove; i < upto; i++) {
11638         SendMoveToProgram(i, cps);
11639     }
11640 }
11641
11642
11643 int
11644 ResurrectChessProgram ()
11645 {
11646      /* The chess program may have exited.
11647         If so, restart it and feed it all the moves made so far. */
11648     static int doInit = 0;
11649
11650     if (appData.noChessProgram) return 1;
11651
11652     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11653         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11654         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11655         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11656     } else {
11657         if (first.pr != NoProc) return 1;
11658         StartChessProgram(&first);
11659     }
11660     InitChessProgram(&first, FALSE);
11661     FeedMovesToProgram(&first, currentMove);
11662
11663     if (!first.sendTime) {
11664         /* can't tell gnuchess what its clock should read,
11665            so we bow to its notion. */
11666         ResetClocks();
11667         timeRemaining[0][currentMove] = whiteTimeRemaining;
11668         timeRemaining[1][currentMove] = blackTimeRemaining;
11669     }
11670
11671     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11672                 appData.icsEngineAnalyze) && first.analysisSupport) {
11673       SendToProgram("analyze\n", &first);
11674       first.analyzing = TRUE;
11675     }
11676     return 1;
11677 }
11678
11679 /*
11680  * Button procedures
11681  */
11682 void
11683 Reset (int redraw, int init)
11684 {
11685     int i;
11686
11687     if (appData.debugMode) {
11688         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11689                 redraw, init, gameMode);
11690     }
11691     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11692     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11693     CleanupTail(); // [HGM] vari: delete any stored variations
11694     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11695     pausing = pauseExamInvalid = FALSE;
11696     startedFromSetupPosition = blackPlaysFirst = FALSE;
11697     firstMove = TRUE;
11698     whiteFlag = blackFlag = FALSE;
11699     userOfferedDraw = FALSE;
11700     hintRequested = bookRequested = FALSE;
11701     first.maybeThinking = FALSE;
11702     second.maybeThinking = FALSE;
11703     first.bookSuspend = FALSE; // [HGM] book
11704     second.bookSuspend = FALSE;
11705     thinkOutput[0] = NULLCHAR;
11706     lastHint[0] = NULLCHAR;
11707     ClearGameInfo(&gameInfo);
11708     gameInfo.variant = StringToVariant(appData.variant);
11709     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11710     ics_user_moved = ics_clock_paused = FALSE;
11711     ics_getting_history = H_FALSE;
11712     ics_gamenum = -1;
11713     white_holding[0] = black_holding[0] = NULLCHAR;
11714     ClearProgramStats();
11715     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11716
11717     ResetFrontEnd();
11718     ClearHighlights();
11719     flipView = appData.flipView;
11720     ClearPremoveHighlights();
11721     gotPremove = FALSE;
11722     alarmSounded = FALSE;
11723     killX = killY = -1; // [HGM] lion
11724
11725     GameEnds(EndOfFile, NULL, GE_PLAYER);
11726     if(appData.serverMovesName != NULL) {
11727         /* [HGM] prepare to make moves file for broadcasting */
11728         clock_t t = clock();
11729         if(serverMoves != NULL) fclose(serverMoves);
11730         serverMoves = fopen(appData.serverMovesName, "r");
11731         if(serverMoves != NULL) {
11732             fclose(serverMoves);
11733             /* delay 15 sec before overwriting, so all clients can see end */
11734             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11735         }
11736         serverMoves = fopen(appData.serverMovesName, "w");
11737     }
11738
11739     ExitAnalyzeMode();
11740     gameMode = BeginningOfGame;
11741     ModeHighlight();
11742     if(appData.icsActive) gameInfo.variant = VariantNormal;
11743     currentMove = forwardMostMove = backwardMostMove = 0;
11744     MarkTargetSquares(1);
11745     InitPosition(redraw);
11746     for (i = 0; i < MAX_MOVES; i++) {
11747         if (commentList[i] != NULL) {
11748             free(commentList[i]);
11749             commentList[i] = NULL;
11750         }
11751     }
11752     ResetClocks();
11753     timeRemaining[0][0] = whiteTimeRemaining;
11754     timeRemaining[1][0] = blackTimeRemaining;
11755
11756     if (first.pr == NoProc) {
11757         StartChessProgram(&first);
11758     }
11759     if (init) {
11760             InitChessProgram(&first, startedFromSetupPosition);
11761     }
11762     DisplayTitle("");
11763     DisplayMessage("", "");
11764     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11765     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11766     ClearMap();        // [HGM] exclude: invalidate map
11767 }
11768
11769 void
11770 AutoPlayGameLoop ()
11771 {
11772     for (;;) {
11773         if (!AutoPlayOneMove())
11774           return;
11775         if (matchMode || appData.timeDelay == 0)
11776           continue;
11777         if (appData.timeDelay < 0)
11778           return;
11779         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11780         break;
11781     }
11782 }
11783
11784 void
11785 AnalyzeNextGame()
11786 {
11787     ReloadGame(1); // next game
11788 }
11789
11790 int
11791 AutoPlayOneMove ()
11792 {
11793     int fromX, fromY, toX, toY;
11794
11795     if (appData.debugMode) {
11796       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11797     }
11798
11799     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11800       return FALSE;
11801
11802     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11803       pvInfoList[currentMove].depth = programStats.depth;
11804       pvInfoList[currentMove].score = programStats.score;
11805       pvInfoList[currentMove].time  = 0;
11806       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11807       else { // append analysis of final position as comment
11808         char buf[MSG_SIZ];
11809         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11810         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11811       }
11812       programStats.depth = 0;
11813     }
11814
11815     if (currentMove >= forwardMostMove) {
11816       if(gameMode == AnalyzeFile) {
11817           if(appData.loadGameIndex == -1) {
11818             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11819           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11820           } else {
11821           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11822         }
11823       }
11824 //      gameMode = EndOfGame;
11825 //      ModeHighlight();
11826
11827       /* [AS] Clear current move marker at the end of a game */
11828       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11829
11830       return FALSE;
11831     }
11832
11833     toX = moveList[currentMove][2] - AAA;
11834     toY = moveList[currentMove][3] - ONE;
11835
11836     if (moveList[currentMove][1] == '@') {
11837         if (appData.highlightLastMove) {
11838             SetHighlights(-1, -1, toX, toY);
11839         }
11840     } else {
11841         int viaX = moveList[currentMove][5] - AAA;
11842         int viaY = moveList[currentMove][6] - ONE;
11843         fromX = moveList[currentMove][0] - AAA;
11844         fromY = moveList[currentMove][1] - ONE;
11845
11846         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11847
11848         if(moveList[currentMove][4] == ';') { // multi-leg
11849             ChessSquare piece = boards[currentMove][viaY][viaX];
11850             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11851             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11852             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11853             boards[currentMove][viaY][viaX] = piece;
11854         } else
11855         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11856
11857         if (appData.highlightLastMove) {
11858             SetHighlights(fromX, fromY, toX, toY);
11859         }
11860     }
11861     DisplayMove(currentMove);
11862     SendMoveToProgram(currentMove++, &first);
11863     DisplayBothClocks();
11864     DrawPosition(FALSE, boards[currentMove]);
11865     // [HGM] PV info: always display, routine tests if empty
11866     DisplayComment(currentMove - 1, commentList[currentMove]);
11867     return TRUE;
11868 }
11869
11870
11871 int
11872 LoadGameOneMove (ChessMove readAhead)
11873 {
11874     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11875     char promoChar = NULLCHAR;
11876     ChessMove moveType;
11877     char move[MSG_SIZ];
11878     char *p, *q;
11879
11880     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11881         gameMode != AnalyzeMode && gameMode != Training) {
11882         gameFileFP = NULL;
11883         return FALSE;
11884     }
11885
11886     yyboardindex = forwardMostMove;
11887     if (readAhead != EndOfFile) {
11888       moveType = readAhead;
11889     } else {
11890       if (gameFileFP == NULL)
11891           return FALSE;
11892       moveType = (ChessMove) Myylex();
11893     }
11894
11895     done = FALSE;
11896     switch (moveType) {
11897       case Comment:
11898         if (appData.debugMode)
11899           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11900         p = yy_text;
11901
11902         /* append the comment but don't display it */
11903         AppendComment(currentMove, p, FALSE);
11904         return TRUE;
11905
11906       case WhiteCapturesEnPassant:
11907       case BlackCapturesEnPassant:
11908       case WhitePromotion:
11909       case BlackPromotion:
11910       case WhiteNonPromotion:
11911       case BlackNonPromotion:
11912       case NormalMove:
11913       case FirstLeg:
11914       case WhiteKingSideCastle:
11915       case WhiteQueenSideCastle:
11916       case BlackKingSideCastle:
11917       case BlackQueenSideCastle:
11918       case WhiteKingSideCastleWild:
11919       case WhiteQueenSideCastleWild:
11920       case BlackKingSideCastleWild:
11921       case BlackQueenSideCastleWild:
11922       /* PUSH Fabien */
11923       case WhiteHSideCastleFR:
11924       case WhiteASideCastleFR:
11925       case BlackHSideCastleFR:
11926       case BlackASideCastleFR:
11927       /* POP Fabien */
11928         if (appData.debugMode)
11929           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11930         fromX = currentMoveString[0] - AAA;
11931         fromY = currentMoveString[1] - ONE;
11932         toX = currentMoveString[2] - AAA;
11933         toY = currentMoveString[3] - ONE;
11934         promoChar = currentMoveString[4];
11935         if(promoChar == ';') promoChar = NULLCHAR;
11936         break;
11937
11938       case WhiteDrop:
11939       case BlackDrop:
11940         if (appData.debugMode)
11941           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11942         fromX = moveType == WhiteDrop ?
11943           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11944         (int) CharToPiece(ToLower(currentMoveString[0]));
11945         fromY = DROP_RANK;
11946         toX = currentMoveString[2] - AAA;
11947         toY = currentMoveString[3] - ONE;
11948         break;
11949
11950       case WhiteWins:
11951       case BlackWins:
11952       case GameIsDrawn:
11953       case GameUnfinished:
11954         if (appData.debugMode)
11955           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11956         p = strchr(yy_text, '{');
11957         if (p == NULL) p = strchr(yy_text, '(');
11958         if (p == NULL) {
11959             p = yy_text;
11960             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11961         } else {
11962             q = strchr(p, *p == '{' ? '}' : ')');
11963             if (q != NULL) *q = NULLCHAR;
11964             p++;
11965         }
11966         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11967         GameEnds(moveType, p, GE_FILE);
11968         done = TRUE;
11969         if (cmailMsgLoaded) {
11970             ClearHighlights();
11971             flipView = WhiteOnMove(currentMove);
11972             if (moveType == GameUnfinished) flipView = !flipView;
11973             if (appData.debugMode)
11974               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11975         }
11976         break;
11977
11978       case EndOfFile:
11979         if (appData.debugMode)
11980           fprintf(debugFP, "Parser hit end of file\n");
11981         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11982           case MT_NONE:
11983           case MT_CHECK:
11984             break;
11985           case MT_CHECKMATE:
11986           case MT_STAINMATE:
11987             if (WhiteOnMove(currentMove)) {
11988                 GameEnds(BlackWins, "Black mates", GE_FILE);
11989             } else {
11990                 GameEnds(WhiteWins, "White mates", GE_FILE);
11991             }
11992             break;
11993           case MT_STALEMATE:
11994             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11995             break;
11996         }
11997         done = TRUE;
11998         break;
11999
12000       case MoveNumberOne:
12001         if (lastLoadGameStart == GNUChessGame) {
12002             /* GNUChessGames have numbers, but they aren't move numbers */
12003             if (appData.debugMode)
12004               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12005                       yy_text, (int) moveType);
12006             return LoadGameOneMove(EndOfFile); /* tail recursion */
12007         }
12008         /* else fall thru */
12009
12010       case XBoardGame:
12011       case GNUChessGame:
12012       case PGNTag:
12013         /* Reached start of next game in file */
12014         if (appData.debugMode)
12015           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12016         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12017           case MT_NONE:
12018           case MT_CHECK:
12019             break;
12020           case MT_CHECKMATE:
12021           case MT_STAINMATE:
12022             if (WhiteOnMove(currentMove)) {
12023                 GameEnds(BlackWins, "Black mates", GE_FILE);
12024             } else {
12025                 GameEnds(WhiteWins, "White mates", GE_FILE);
12026             }
12027             break;
12028           case MT_STALEMATE:
12029             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12030             break;
12031         }
12032         done = TRUE;
12033         break;
12034
12035       case PositionDiagram:     /* should not happen; ignore */
12036       case ElapsedTime:         /* ignore */
12037       case NAG:                 /* ignore */
12038         if (appData.debugMode)
12039           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12040                   yy_text, (int) moveType);
12041         return LoadGameOneMove(EndOfFile); /* tail recursion */
12042
12043       case IllegalMove:
12044         if (appData.testLegality) {
12045             if (appData.debugMode)
12046               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12047             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12048                     (forwardMostMove / 2) + 1,
12049                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12050             DisplayError(move, 0);
12051             done = TRUE;
12052         } else {
12053             if (appData.debugMode)
12054               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12055                       yy_text, currentMoveString);
12056             fromX = currentMoveString[0] - AAA;
12057             fromY = currentMoveString[1] - ONE;
12058             toX = currentMoveString[2] - AAA;
12059             toY = currentMoveString[3] - ONE;
12060             promoChar = currentMoveString[4];
12061         }
12062         break;
12063
12064       case AmbiguousMove:
12065         if (appData.debugMode)
12066           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12067         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12068                 (forwardMostMove / 2) + 1,
12069                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12070         DisplayError(move, 0);
12071         done = TRUE;
12072         break;
12073
12074       default:
12075       case ImpossibleMove:
12076         if (appData.debugMode)
12077           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12078         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12079                 (forwardMostMove / 2) + 1,
12080                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12081         DisplayError(move, 0);
12082         done = TRUE;
12083         break;
12084     }
12085
12086     if (done) {
12087         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12088             DrawPosition(FALSE, boards[currentMove]);
12089             DisplayBothClocks();
12090             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12091               DisplayComment(currentMove - 1, commentList[currentMove]);
12092         }
12093         (void) StopLoadGameTimer();
12094         gameFileFP = NULL;
12095         cmailOldMove = forwardMostMove;
12096         return FALSE;
12097     } else {
12098         /* currentMoveString is set as a side-effect of yylex */
12099
12100         thinkOutput[0] = NULLCHAR;
12101         MakeMove(fromX, fromY, toX, toY, promoChar);
12102         killX = killY = -1; // [HGM] lion: used up
12103         currentMove = forwardMostMove;
12104         return TRUE;
12105     }
12106 }
12107
12108 /* Load the nth game from the given file */
12109 int
12110 LoadGameFromFile (char *filename, int n, char *title, int useList)
12111 {
12112     FILE *f;
12113     char buf[MSG_SIZ];
12114
12115     if (strcmp(filename, "-") == 0) {
12116         f = stdin;
12117         title = "stdin";
12118     } else {
12119         f = fopen(filename, "rb");
12120         if (f == NULL) {
12121           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12122             DisplayError(buf, errno);
12123             return FALSE;
12124         }
12125     }
12126     if (fseek(f, 0, 0) == -1) {
12127         /* f is not seekable; probably a pipe */
12128         useList = FALSE;
12129     }
12130     if (useList && n == 0) {
12131         int error = GameListBuild(f);
12132         if (error) {
12133             DisplayError(_("Cannot build game list"), error);
12134         } else if (!ListEmpty(&gameList) &&
12135                    ((ListGame *) gameList.tailPred)->number > 1) {
12136             GameListPopUp(f, title);
12137             return TRUE;
12138         }
12139         GameListDestroy();
12140         n = 1;
12141     }
12142     if (n == 0) n = 1;
12143     return LoadGame(f, n, title, FALSE);
12144 }
12145
12146
12147 void
12148 MakeRegisteredMove ()
12149 {
12150     int fromX, fromY, toX, toY;
12151     char promoChar;
12152     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12153         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12154           case CMAIL_MOVE:
12155           case CMAIL_DRAW:
12156             if (appData.debugMode)
12157               fprintf(debugFP, "Restoring %s for game %d\n",
12158                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12159
12160             thinkOutput[0] = NULLCHAR;
12161             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12162             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12163             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12164             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12165             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12166             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12167             MakeMove(fromX, fromY, toX, toY, promoChar);
12168             ShowMove(fromX, fromY, toX, toY);
12169
12170             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12171               case MT_NONE:
12172               case MT_CHECK:
12173                 break;
12174
12175               case MT_CHECKMATE:
12176               case MT_STAINMATE:
12177                 if (WhiteOnMove(currentMove)) {
12178                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12179                 } else {
12180                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12181                 }
12182                 break;
12183
12184               case MT_STALEMATE:
12185                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12186                 break;
12187             }
12188
12189             break;
12190
12191           case CMAIL_RESIGN:
12192             if (WhiteOnMove(currentMove)) {
12193                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12194             } else {
12195                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12196             }
12197             break;
12198
12199           case CMAIL_ACCEPT:
12200             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12201             break;
12202
12203           default:
12204             break;
12205         }
12206     }
12207
12208     return;
12209 }
12210
12211 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12212 int
12213 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12214 {
12215     int retVal;
12216
12217     if (gameNumber > nCmailGames) {
12218         DisplayError(_("No more games in this message"), 0);
12219         return FALSE;
12220     }
12221     if (f == lastLoadGameFP) {
12222         int offset = gameNumber - lastLoadGameNumber;
12223         if (offset == 0) {
12224             cmailMsg[0] = NULLCHAR;
12225             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12226                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12227                 nCmailMovesRegistered--;
12228             }
12229             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12230             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12231                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12232             }
12233         } else {
12234             if (! RegisterMove()) return FALSE;
12235         }
12236     }
12237
12238     retVal = LoadGame(f, gameNumber, title, useList);
12239
12240     /* Make move registered during previous look at this game, if any */
12241     MakeRegisteredMove();
12242
12243     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12244         commentList[currentMove]
12245           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12246         DisplayComment(currentMove - 1, commentList[currentMove]);
12247     }
12248
12249     return retVal;
12250 }
12251
12252 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12253 int
12254 ReloadGame (int offset)
12255 {
12256     int gameNumber = lastLoadGameNumber + offset;
12257     if (lastLoadGameFP == NULL) {
12258         DisplayError(_("No game has been loaded yet"), 0);
12259         return FALSE;
12260     }
12261     if (gameNumber <= 0) {
12262         DisplayError(_("Can't back up any further"), 0);
12263         return FALSE;
12264     }
12265     if (cmailMsgLoaded) {
12266         return CmailLoadGame(lastLoadGameFP, gameNumber,
12267                              lastLoadGameTitle, lastLoadGameUseList);
12268     } else {
12269         return LoadGame(lastLoadGameFP, gameNumber,
12270                         lastLoadGameTitle, lastLoadGameUseList);
12271     }
12272 }
12273
12274 int keys[EmptySquare+1];
12275
12276 int
12277 PositionMatches (Board b1, Board b2)
12278 {
12279     int r, f, sum=0;
12280     switch(appData.searchMode) {
12281         case 1: return CompareWithRights(b1, b2);
12282         case 2:
12283             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12284                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12285             }
12286             return TRUE;
12287         case 3:
12288             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12289               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12290                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12291             }
12292             return sum==0;
12293         case 4:
12294             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12295                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12296             }
12297             return sum==0;
12298     }
12299     return TRUE;
12300 }
12301
12302 #define Q_PROMO  4
12303 #define Q_EP     3
12304 #define Q_BCASTL 2
12305 #define Q_WCASTL 1
12306
12307 int pieceList[256], quickBoard[256];
12308 ChessSquare pieceType[256] = { EmptySquare };
12309 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12310 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12311 int soughtTotal, turn;
12312 Boolean epOK, flipSearch;
12313
12314 typedef struct {
12315     unsigned char piece, to;
12316 } Move;
12317
12318 #define DSIZE (250000)
12319
12320 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12321 Move *moveDatabase = initialSpace;
12322 unsigned int movePtr, dataSize = DSIZE;
12323
12324 int
12325 MakePieceList (Board board, int *counts)
12326 {
12327     int r, f, n=Q_PROMO, total=0;
12328     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12329     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12330         int sq = f + (r<<4);
12331         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12332             quickBoard[sq] = ++n;
12333             pieceList[n] = sq;
12334             pieceType[n] = board[r][f];
12335             counts[board[r][f]]++;
12336             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12337             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12338             total++;
12339         }
12340     }
12341     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12342     return total;
12343 }
12344
12345 void
12346 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12347 {
12348     int sq = fromX + (fromY<<4);
12349     int piece = quickBoard[sq], rook;
12350     quickBoard[sq] = 0;
12351     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12352     if(piece == pieceList[1] && fromY == toY) {
12353       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12354         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12355         moveDatabase[movePtr++].piece = Q_WCASTL;
12356         quickBoard[sq] = piece;
12357         piece = quickBoard[from]; quickBoard[from] = 0;
12358         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12359       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12360         quickBoard[sq] = 0; // remove Rook
12361         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12362         moveDatabase[movePtr++].piece = Q_WCASTL;
12363         quickBoard[sq] = pieceList[1]; // put King
12364         piece = rook;
12365         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12366       }
12367     } else
12368     if(piece == pieceList[2] && fromY == toY) {
12369       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12370         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12371         moveDatabase[movePtr++].piece = Q_BCASTL;
12372         quickBoard[sq] = piece;
12373         piece = quickBoard[from]; quickBoard[from] = 0;
12374         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12375       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12376         quickBoard[sq] = 0; // remove Rook
12377         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12378         moveDatabase[movePtr++].piece = Q_BCASTL;
12379         quickBoard[sq] = pieceList[2]; // put King
12380         piece = rook;
12381         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12382       }
12383     } else
12384     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12385         quickBoard[(fromY<<4)+toX] = 0;
12386         moveDatabase[movePtr].piece = Q_EP;
12387         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12388         moveDatabase[movePtr].to = sq;
12389     } else
12390     if(promoPiece != pieceType[piece]) {
12391         moveDatabase[movePtr++].piece = Q_PROMO;
12392         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12393     }
12394     moveDatabase[movePtr].piece = piece;
12395     quickBoard[sq] = piece;
12396     movePtr++;
12397 }
12398
12399 int
12400 PackGame (Board board)
12401 {
12402     Move *newSpace = NULL;
12403     moveDatabase[movePtr].piece = 0; // terminate previous game
12404     if(movePtr > dataSize) {
12405         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12406         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12407         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12408         if(newSpace) {
12409             int i;
12410             Move *p = moveDatabase, *q = newSpace;
12411             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12412             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12413             moveDatabase = newSpace;
12414         } else { // calloc failed, we must be out of memory. Too bad...
12415             dataSize = 0; // prevent calloc events for all subsequent games
12416             return 0;     // and signal this one isn't cached
12417         }
12418     }
12419     movePtr++;
12420     MakePieceList(board, counts);
12421     return movePtr;
12422 }
12423
12424 int
12425 QuickCompare (Board board, int *minCounts, int *maxCounts)
12426 {   // compare according to search mode
12427     int r, f;
12428     switch(appData.searchMode)
12429     {
12430       case 1: // exact position match
12431         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12432         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12433             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12434         }
12435         break;
12436       case 2: // can have extra material on empty squares
12437         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12438             if(board[r][f] == EmptySquare) continue;
12439             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12440         }
12441         break;
12442       case 3: // material with exact Pawn structure
12443         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12444             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12445             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12446         } // fall through to material comparison
12447       case 4: // exact material
12448         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12449         break;
12450       case 6: // material range with given imbalance
12451         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12452         // fall through to range comparison
12453       case 5: // material range
12454         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12455     }
12456     return TRUE;
12457 }
12458
12459 int
12460 QuickScan (Board board, Move *move)
12461 {   // reconstruct game,and compare all positions in it
12462     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12463     do {
12464         int piece = move->piece;
12465         int to = move->to, from = pieceList[piece];
12466         if(found < 0) { // if already found just scan to game end for final piece count
12467           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12468            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12469            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12470                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12471             ) {
12472             static int lastCounts[EmptySquare+1];
12473             int i;
12474             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12475             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12476           } else stretch = 0;
12477           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12478           if(found >= 0 && !appData.minPieces) return found;
12479         }
12480         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12481           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12482           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12483             piece = (++move)->piece;
12484             from = pieceList[piece];
12485             counts[pieceType[piece]]--;
12486             pieceType[piece] = (ChessSquare) move->to;
12487             counts[move->to]++;
12488           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12489             counts[pieceType[quickBoard[to]]]--;
12490             quickBoard[to] = 0; total--;
12491             move++;
12492             continue;
12493           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12494             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12495             from  = pieceList[piece]; // so this must be King
12496             quickBoard[from] = 0;
12497             pieceList[piece] = to;
12498             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12499             quickBoard[from] = 0; // rook
12500             quickBoard[to] = piece;
12501             to = move->to; piece = move->piece;
12502             goto aftercastle;
12503           }
12504         }
12505         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12506         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12507         quickBoard[from] = 0;
12508       aftercastle:
12509         quickBoard[to] = piece;
12510         pieceList[piece] = to;
12511         cnt++; turn ^= 3;
12512         move++;
12513     } while(1);
12514 }
12515
12516 void
12517 InitSearch ()
12518 {
12519     int r, f;
12520     flipSearch = FALSE;
12521     CopyBoard(soughtBoard, boards[currentMove]);
12522     soughtTotal = MakePieceList(soughtBoard, maxSought);
12523     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12524     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12525     CopyBoard(reverseBoard, boards[currentMove]);
12526     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12527         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12528         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12529         reverseBoard[r][f] = piece;
12530     }
12531     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12532     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12533     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12534                  || (boards[currentMove][CASTLING][2] == NoRights ||
12535                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12536                  && (boards[currentMove][CASTLING][5] == NoRights ||
12537                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12538       ) {
12539         flipSearch = TRUE;
12540         CopyBoard(flipBoard, soughtBoard);
12541         CopyBoard(rotateBoard, reverseBoard);
12542         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12543             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12544             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12545         }
12546     }
12547     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12548     if(appData.searchMode >= 5) {
12549         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12550         MakePieceList(soughtBoard, minSought);
12551         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12552     }
12553     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12554         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12555 }
12556
12557 GameInfo dummyInfo;
12558 static int creatingBook;
12559
12560 int
12561 GameContainsPosition (FILE *f, ListGame *lg)
12562 {
12563     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12564     int fromX, fromY, toX, toY;
12565     char promoChar;
12566     static int initDone=FALSE;
12567
12568     // weed out games based on numerical tag comparison
12569     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12570     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12571     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12572     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12573     if(!initDone) {
12574         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12575         initDone = TRUE;
12576     }
12577     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12578     else CopyBoard(boards[scratch], initialPosition); // default start position
12579     if(lg->moves) {
12580         turn = btm + 1;
12581         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12582         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12583     }
12584     if(btm) plyNr++;
12585     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12586     fseek(f, lg->offset, 0);
12587     yynewfile(f);
12588     while(1) {
12589         yyboardindex = scratch;
12590         quickFlag = plyNr+1;
12591         next = Myylex();
12592         quickFlag = 0;
12593         switch(next) {
12594             case PGNTag:
12595                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12596             default:
12597                 continue;
12598
12599             case XBoardGame:
12600             case GNUChessGame:
12601                 if(plyNr) return -1; // after we have seen moves, this is for new game
12602               continue;
12603
12604             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12605             case ImpossibleMove:
12606             case WhiteWins: // game ends here with these four
12607             case BlackWins:
12608             case GameIsDrawn:
12609             case GameUnfinished:
12610                 return -1;
12611
12612             case IllegalMove:
12613                 if(appData.testLegality) return -1;
12614             case WhiteCapturesEnPassant:
12615             case BlackCapturesEnPassant:
12616             case WhitePromotion:
12617             case BlackPromotion:
12618             case WhiteNonPromotion:
12619             case BlackNonPromotion:
12620             case NormalMove:
12621             case FirstLeg:
12622             case WhiteKingSideCastle:
12623             case WhiteQueenSideCastle:
12624             case BlackKingSideCastle:
12625             case BlackQueenSideCastle:
12626             case WhiteKingSideCastleWild:
12627             case WhiteQueenSideCastleWild:
12628             case BlackKingSideCastleWild:
12629             case BlackQueenSideCastleWild:
12630             case WhiteHSideCastleFR:
12631             case WhiteASideCastleFR:
12632             case BlackHSideCastleFR:
12633             case BlackASideCastleFR:
12634                 fromX = currentMoveString[0] - AAA;
12635                 fromY = currentMoveString[1] - ONE;
12636                 toX = currentMoveString[2] - AAA;
12637                 toY = currentMoveString[3] - ONE;
12638                 promoChar = currentMoveString[4];
12639                 break;
12640             case WhiteDrop:
12641             case BlackDrop:
12642                 fromX = next == WhiteDrop ?
12643                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12644                   (int) CharToPiece(ToLower(currentMoveString[0]));
12645                 fromY = DROP_RANK;
12646                 toX = currentMoveString[2] - AAA;
12647                 toY = currentMoveString[3] - ONE;
12648                 promoChar = 0;
12649                 break;
12650         }
12651         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12652         plyNr++;
12653         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12654         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12655         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12656         if(appData.findMirror) {
12657             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12658             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12659         }
12660     }
12661 }
12662
12663 /* Load the nth game from open file f */
12664 int
12665 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12666 {
12667     ChessMove cm;
12668     char buf[MSG_SIZ];
12669     int gn = gameNumber;
12670     ListGame *lg = NULL;
12671     int numPGNTags = 0;
12672     int err, pos = -1;
12673     GameMode oldGameMode;
12674     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12675
12676     if (appData.debugMode)
12677         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12678
12679     if (gameMode == Training )
12680         SetTrainingModeOff();
12681
12682     oldGameMode = gameMode;
12683     if (gameMode != BeginningOfGame) {
12684       Reset(FALSE, TRUE);
12685     }
12686     killX = killY = -1; // [HGM] lion: in case we did not Reset
12687
12688     gameFileFP = f;
12689     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12690         fclose(lastLoadGameFP);
12691     }
12692
12693     if (useList) {
12694         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12695
12696         if (lg) {
12697             fseek(f, lg->offset, 0);
12698             GameListHighlight(gameNumber);
12699             pos = lg->position;
12700             gn = 1;
12701         }
12702         else {
12703             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12704               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12705             else
12706             DisplayError(_("Game number out of range"), 0);
12707             return FALSE;
12708         }
12709     } else {
12710         GameListDestroy();
12711         if (fseek(f, 0, 0) == -1) {
12712             if (f == lastLoadGameFP ?
12713                 gameNumber == lastLoadGameNumber + 1 :
12714                 gameNumber == 1) {
12715                 gn = 1;
12716             } else {
12717                 DisplayError(_("Can't seek on game file"), 0);
12718                 return FALSE;
12719             }
12720         }
12721     }
12722     lastLoadGameFP = f;
12723     lastLoadGameNumber = gameNumber;
12724     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12725     lastLoadGameUseList = useList;
12726
12727     yynewfile(f);
12728
12729     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12730       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12731                 lg->gameInfo.black);
12732             DisplayTitle(buf);
12733     } else if (*title != NULLCHAR) {
12734         if (gameNumber > 1) {
12735           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12736             DisplayTitle(buf);
12737         } else {
12738             DisplayTitle(title);
12739         }
12740     }
12741
12742     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12743         gameMode = PlayFromGameFile;
12744         ModeHighlight();
12745     }
12746
12747     currentMove = forwardMostMove = backwardMostMove = 0;
12748     CopyBoard(boards[0], initialPosition);
12749     StopClocks();
12750
12751     /*
12752      * Skip the first gn-1 games in the file.
12753      * Also skip over anything that precedes an identifiable
12754      * start of game marker, to avoid being confused by
12755      * garbage at the start of the file.  Currently
12756      * recognized start of game markers are the move number "1",
12757      * the pattern "gnuchess .* game", the pattern
12758      * "^[#;%] [^ ]* game file", and a PGN tag block.
12759      * A game that starts with one of the latter two patterns
12760      * will also have a move number 1, possibly
12761      * following a position diagram.
12762      * 5-4-02: Let's try being more lenient and allowing a game to
12763      * start with an unnumbered move.  Does that break anything?
12764      */
12765     cm = lastLoadGameStart = EndOfFile;
12766     while (gn > 0) {
12767         yyboardindex = forwardMostMove;
12768         cm = (ChessMove) Myylex();
12769         switch (cm) {
12770           case EndOfFile:
12771             if (cmailMsgLoaded) {
12772                 nCmailGames = CMAIL_MAX_GAMES - gn;
12773             } else {
12774                 Reset(TRUE, TRUE);
12775                 DisplayError(_("Game not found in file"), 0);
12776             }
12777             return FALSE;
12778
12779           case GNUChessGame:
12780           case XBoardGame:
12781             gn--;
12782             lastLoadGameStart = cm;
12783             break;
12784
12785           case MoveNumberOne:
12786             switch (lastLoadGameStart) {
12787               case GNUChessGame:
12788               case XBoardGame:
12789               case PGNTag:
12790                 break;
12791               case MoveNumberOne:
12792               case EndOfFile:
12793                 gn--;           /* count this game */
12794                 lastLoadGameStart = cm;
12795                 break;
12796               default:
12797                 /* impossible */
12798                 break;
12799             }
12800             break;
12801
12802           case PGNTag:
12803             switch (lastLoadGameStart) {
12804               case GNUChessGame:
12805               case PGNTag:
12806               case MoveNumberOne:
12807               case EndOfFile:
12808                 gn--;           /* count this game */
12809                 lastLoadGameStart = cm;
12810                 break;
12811               case XBoardGame:
12812                 lastLoadGameStart = cm; /* game counted already */
12813                 break;
12814               default:
12815                 /* impossible */
12816                 break;
12817             }
12818             if (gn > 0) {
12819                 do {
12820                     yyboardindex = forwardMostMove;
12821                     cm = (ChessMove) Myylex();
12822                 } while (cm == PGNTag || cm == Comment);
12823             }
12824             break;
12825
12826           case WhiteWins:
12827           case BlackWins:
12828           case GameIsDrawn:
12829             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12830                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12831                     != CMAIL_OLD_RESULT) {
12832                     nCmailResults ++ ;
12833                     cmailResult[  CMAIL_MAX_GAMES
12834                                 - gn - 1] = CMAIL_OLD_RESULT;
12835                 }
12836             }
12837             break;
12838
12839           case NormalMove:
12840           case FirstLeg:
12841             /* Only a NormalMove can be at the start of a game
12842              * without a position diagram. */
12843             if (lastLoadGameStart == EndOfFile ) {
12844               gn--;
12845               lastLoadGameStart = MoveNumberOne;
12846             }
12847             break;
12848
12849           default:
12850             break;
12851         }
12852     }
12853
12854     if (appData.debugMode)
12855       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12856
12857     if (cm == XBoardGame) {
12858         /* Skip any header junk before position diagram and/or move 1 */
12859         for (;;) {
12860             yyboardindex = forwardMostMove;
12861             cm = (ChessMove) Myylex();
12862
12863             if (cm == EndOfFile ||
12864                 cm == GNUChessGame || cm == XBoardGame) {
12865                 /* Empty game; pretend end-of-file and handle later */
12866                 cm = EndOfFile;
12867                 break;
12868             }
12869
12870             if (cm == MoveNumberOne || cm == PositionDiagram ||
12871                 cm == PGNTag || cm == Comment)
12872               break;
12873         }
12874     } else if (cm == GNUChessGame) {
12875         if (gameInfo.event != NULL) {
12876             free(gameInfo.event);
12877         }
12878         gameInfo.event = StrSave(yy_text);
12879     }
12880
12881     startedFromSetupPosition = FALSE;
12882     while (cm == PGNTag) {
12883         if (appData.debugMode)
12884           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12885         err = ParsePGNTag(yy_text, &gameInfo);
12886         if (!err) numPGNTags++;
12887
12888         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12889         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12890             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12891             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12892             InitPosition(TRUE);
12893             oldVariant = gameInfo.variant;
12894             if (appData.debugMode)
12895               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12896         }
12897
12898
12899         if (gameInfo.fen != NULL) {
12900           Board initial_position;
12901           startedFromSetupPosition = TRUE;
12902           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12903             Reset(TRUE, TRUE);
12904             DisplayError(_("Bad FEN position in file"), 0);
12905             return FALSE;
12906           }
12907           CopyBoard(boards[0], initial_position);
12908           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12909             CopyBoard(initialPosition, initial_position);
12910           if (blackPlaysFirst) {
12911             currentMove = forwardMostMove = backwardMostMove = 1;
12912             CopyBoard(boards[1], initial_position);
12913             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12914             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12915             timeRemaining[0][1] = whiteTimeRemaining;
12916             timeRemaining[1][1] = blackTimeRemaining;
12917             if (commentList[0] != NULL) {
12918               commentList[1] = commentList[0];
12919               commentList[0] = NULL;
12920             }
12921           } else {
12922             currentMove = forwardMostMove = backwardMostMove = 0;
12923           }
12924           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12925           {   int i;
12926               initialRulePlies = FENrulePlies;
12927               for( i=0; i< nrCastlingRights; i++ )
12928                   initialRights[i] = initial_position[CASTLING][i];
12929           }
12930           yyboardindex = forwardMostMove;
12931           free(gameInfo.fen);
12932           gameInfo.fen = NULL;
12933         }
12934
12935         yyboardindex = forwardMostMove;
12936         cm = (ChessMove) Myylex();
12937
12938         /* Handle comments interspersed among the tags */
12939         while (cm == Comment) {
12940             char *p;
12941             if (appData.debugMode)
12942               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12943             p = yy_text;
12944             AppendComment(currentMove, p, FALSE);
12945             yyboardindex = forwardMostMove;
12946             cm = (ChessMove) Myylex();
12947         }
12948     }
12949
12950     /* don't rely on existence of Event tag since if game was
12951      * pasted from clipboard the Event tag may not exist
12952      */
12953     if (numPGNTags > 0){
12954         char *tags;
12955         if (gameInfo.variant == VariantNormal) {
12956           VariantClass v = StringToVariant(gameInfo.event);
12957           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12958           if(v < VariantShogi) gameInfo.variant = v;
12959         }
12960         if (!matchMode) {
12961           if( appData.autoDisplayTags ) {
12962             tags = PGNTags(&gameInfo);
12963             TagsPopUp(tags, CmailMsg());
12964             free(tags);
12965           }
12966         }
12967     } else {
12968         /* Make something up, but don't display it now */
12969         SetGameInfo();
12970         TagsPopDown();
12971     }
12972
12973     if (cm == PositionDiagram) {
12974         int i, j;
12975         char *p;
12976         Board initial_position;
12977
12978         if (appData.debugMode)
12979           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12980
12981         if (!startedFromSetupPosition) {
12982             p = yy_text;
12983             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12984               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12985                 switch (*p) {
12986                   case '{':
12987                   case '[':
12988                   case '-':
12989                   case ' ':
12990                   case '\t':
12991                   case '\n':
12992                   case '\r':
12993                     break;
12994                   default:
12995                     initial_position[i][j++] = CharToPiece(*p);
12996                     break;
12997                 }
12998             while (*p == ' ' || *p == '\t' ||
12999                    *p == '\n' || *p == '\r') p++;
13000
13001             if (strncmp(p, "black", strlen("black"))==0)
13002               blackPlaysFirst = TRUE;
13003             else
13004               blackPlaysFirst = FALSE;
13005             startedFromSetupPosition = TRUE;
13006
13007             CopyBoard(boards[0], initial_position);
13008             if (blackPlaysFirst) {
13009                 currentMove = forwardMostMove = backwardMostMove = 1;
13010                 CopyBoard(boards[1], initial_position);
13011                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13012                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13013                 timeRemaining[0][1] = whiteTimeRemaining;
13014                 timeRemaining[1][1] = blackTimeRemaining;
13015                 if (commentList[0] != NULL) {
13016                     commentList[1] = commentList[0];
13017                     commentList[0] = NULL;
13018                 }
13019             } else {
13020                 currentMove = forwardMostMove = backwardMostMove = 0;
13021             }
13022         }
13023         yyboardindex = forwardMostMove;
13024         cm = (ChessMove) Myylex();
13025     }
13026
13027   if(!creatingBook) {
13028     if (first.pr == NoProc) {
13029         StartChessProgram(&first);
13030     }
13031     InitChessProgram(&first, FALSE);
13032     SendToProgram("force\n", &first);
13033     if (startedFromSetupPosition) {
13034         SendBoard(&first, forwardMostMove);
13035     if (appData.debugMode) {
13036         fprintf(debugFP, "Load Game\n");
13037     }
13038         DisplayBothClocks();
13039     }
13040   }
13041
13042     /* [HGM] server: flag to write setup moves in broadcast file as one */
13043     loadFlag = appData.suppressLoadMoves;
13044
13045     while (cm == Comment) {
13046         char *p;
13047         if (appData.debugMode)
13048           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13049         p = yy_text;
13050         AppendComment(currentMove, p, FALSE);
13051         yyboardindex = forwardMostMove;
13052         cm = (ChessMove) Myylex();
13053     }
13054
13055     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13056         cm == WhiteWins || cm == BlackWins ||
13057         cm == GameIsDrawn || cm == GameUnfinished) {
13058         DisplayMessage("", _("No moves in game"));
13059         if (cmailMsgLoaded) {
13060             if (appData.debugMode)
13061               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13062             ClearHighlights();
13063             flipView = FALSE;
13064         }
13065         DrawPosition(FALSE, boards[currentMove]);
13066         DisplayBothClocks();
13067         gameMode = EditGame;
13068         ModeHighlight();
13069         gameFileFP = NULL;
13070         cmailOldMove = 0;
13071         return TRUE;
13072     }
13073
13074     // [HGM] PV info: routine tests if comment empty
13075     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13076         DisplayComment(currentMove - 1, commentList[currentMove]);
13077     }
13078     if (!matchMode && appData.timeDelay != 0)
13079       DrawPosition(FALSE, boards[currentMove]);
13080
13081     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13082       programStats.ok_to_send = 1;
13083     }
13084
13085     /* if the first token after the PGN tags is a move
13086      * and not move number 1, retrieve it from the parser
13087      */
13088     if (cm != MoveNumberOne)
13089         LoadGameOneMove(cm);
13090
13091     /* load the remaining moves from the file */
13092     while (LoadGameOneMove(EndOfFile)) {
13093       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13094       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13095     }
13096
13097     /* rewind to the start of the game */
13098     currentMove = backwardMostMove;
13099
13100     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13101
13102     if (oldGameMode == AnalyzeFile) {
13103       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13104       AnalyzeFileEvent();
13105     } else
13106     if (oldGameMode == AnalyzeMode) {
13107       AnalyzeFileEvent();
13108     }
13109
13110     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13111         long int w, b; // [HGM] adjourn: restore saved clock times
13112         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13113         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13114             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13115             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13116         }
13117     }
13118
13119     if(creatingBook) return TRUE;
13120     if (!matchMode && pos > 0) {
13121         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13122     } else
13123     if (matchMode || appData.timeDelay == 0) {
13124       ToEndEvent();
13125     } else if (appData.timeDelay > 0) {
13126       AutoPlayGameLoop();
13127     }
13128
13129     if (appData.debugMode)
13130         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13131
13132     loadFlag = 0; /* [HGM] true game starts */
13133     return TRUE;
13134 }
13135
13136 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13137 int
13138 ReloadPosition (int offset)
13139 {
13140     int positionNumber = lastLoadPositionNumber + offset;
13141     if (lastLoadPositionFP == NULL) {
13142         DisplayError(_("No position has been loaded yet"), 0);
13143         return FALSE;
13144     }
13145     if (positionNumber <= 0) {
13146         DisplayError(_("Can't back up any further"), 0);
13147         return FALSE;
13148     }
13149     return LoadPosition(lastLoadPositionFP, positionNumber,
13150                         lastLoadPositionTitle);
13151 }
13152
13153 /* Load the nth position from the given file */
13154 int
13155 LoadPositionFromFile (char *filename, int n, char *title)
13156 {
13157     FILE *f;
13158     char buf[MSG_SIZ];
13159
13160     if (strcmp(filename, "-") == 0) {
13161         return LoadPosition(stdin, n, "stdin");
13162     } else {
13163         f = fopen(filename, "rb");
13164         if (f == NULL) {
13165             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13166             DisplayError(buf, errno);
13167             return FALSE;
13168         } else {
13169             return LoadPosition(f, n, title);
13170         }
13171     }
13172 }
13173
13174 /* Load the nth position from the given open file, and close it */
13175 int
13176 LoadPosition (FILE *f, int positionNumber, char *title)
13177 {
13178     char *p, line[MSG_SIZ];
13179     Board initial_position;
13180     int i, j, fenMode, pn;
13181
13182     if (gameMode == Training )
13183         SetTrainingModeOff();
13184
13185     if (gameMode != BeginningOfGame) {
13186         Reset(FALSE, TRUE);
13187     }
13188     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13189         fclose(lastLoadPositionFP);
13190     }
13191     if (positionNumber == 0) positionNumber = 1;
13192     lastLoadPositionFP = f;
13193     lastLoadPositionNumber = positionNumber;
13194     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13195     if (first.pr == NoProc && !appData.noChessProgram) {
13196       StartChessProgram(&first);
13197       InitChessProgram(&first, FALSE);
13198     }
13199     pn = positionNumber;
13200     if (positionNumber < 0) {
13201         /* Negative position number means to seek to that byte offset */
13202         if (fseek(f, -positionNumber, 0) == -1) {
13203             DisplayError(_("Can't seek on position file"), 0);
13204             return FALSE;
13205         };
13206         pn = 1;
13207     } else {
13208         if (fseek(f, 0, 0) == -1) {
13209             if (f == lastLoadPositionFP ?
13210                 positionNumber == lastLoadPositionNumber + 1 :
13211                 positionNumber == 1) {
13212                 pn = 1;
13213             } else {
13214                 DisplayError(_("Can't seek on position file"), 0);
13215                 return FALSE;
13216             }
13217         }
13218     }
13219     /* See if this file is FEN or old-style xboard */
13220     if (fgets(line, MSG_SIZ, f) == NULL) {
13221         DisplayError(_("Position not found in file"), 0);
13222         return FALSE;
13223     }
13224     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13225     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13226
13227     if (pn >= 2) {
13228         if (fenMode || line[0] == '#') pn--;
13229         while (pn > 0) {
13230             /* skip positions before number pn */
13231             if (fgets(line, MSG_SIZ, f) == NULL) {
13232                 Reset(TRUE, TRUE);
13233                 DisplayError(_("Position not found in file"), 0);
13234                 return FALSE;
13235             }
13236             if (fenMode || line[0] == '#') pn--;
13237         }
13238     }
13239
13240     if (fenMode) {
13241         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13242             DisplayError(_("Bad FEN position in file"), 0);
13243             return FALSE;
13244         }
13245     } else {
13246         (void) fgets(line, MSG_SIZ, f);
13247         (void) fgets(line, MSG_SIZ, f);
13248
13249         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13250             (void) fgets(line, MSG_SIZ, f);
13251             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13252                 if (*p == ' ')
13253                   continue;
13254                 initial_position[i][j++] = CharToPiece(*p);
13255             }
13256         }
13257
13258         blackPlaysFirst = FALSE;
13259         if (!feof(f)) {
13260             (void) fgets(line, MSG_SIZ, f);
13261             if (strncmp(line, "black", strlen("black"))==0)
13262               blackPlaysFirst = TRUE;
13263         }
13264     }
13265     startedFromSetupPosition = TRUE;
13266
13267     CopyBoard(boards[0], initial_position);
13268     if (blackPlaysFirst) {
13269         currentMove = forwardMostMove = backwardMostMove = 1;
13270         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13271         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13272         CopyBoard(boards[1], initial_position);
13273         DisplayMessage("", _("Black to play"));
13274     } else {
13275         currentMove = forwardMostMove = backwardMostMove = 0;
13276         DisplayMessage("", _("White to play"));
13277     }
13278     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13279     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13280         SendToProgram("force\n", &first);
13281         SendBoard(&first, forwardMostMove);
13282     }
13283     if (appData.debugMode) {
13284 int i, j;
13285   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13286   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13287         fprintf(debugFP, "Load Position\n");
13288     }
13289
13290     if (positionNumber > 1) {
13291       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13292         DisplayTitle(line);
13293     } else {
13294         DisplayTitle(title);
13295     }
13296     gameMode = EditGame;
13297     ModeHighlight();
13298     ResetClocks();
13299     timeRemaining[0][1] = whiteTimeRemaining;
13300     timeRemaining[1][1] = blackTimeRemaining;
13301     DrawPosition(FALSE, boards[currentMove]);
13302
13303     return TRUE;
13304 }
13305
13306
13307 void
13308 CopyPlayerNameIntoFileName (char **dest, char *src)
13309 {
13310     while (*src != NULLCHAR && *src != ',') {
13311         if (*src == ' ') {
13312             *(*dest)++ = '_';
13313             src++;
13314         } else {
13315             *(*dest)++ = *src++;
13316         }
13317     }
13318 }
13319
13320 char *
13321 DefaultFileName (char *ext)
13322 {
13323     static char def[MSG_SIZ];
13324     char *p;
13325
13326     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13327         p = def;
13328         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13329         *p++ = '-';
13330         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13331         *p++ = '.';
13332         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13333     } else {
13334         def[0] = NULLCHAR;
13335     }
13336     return def;
13337 }
13338
13339 /* Save the current game to the given file */
13340 int
13341 SaveGameToFile (char *filename, int append)
13342 {
13343     FILE *f;
13344     char buf[MSG_SIZ];
13345     int result, i, t,tot=0;
13346
13347     if (strcmp(filename, "-") == 0) {
13348         return SaveGame(stdout, 0, NULL);
13349     } else {
13350         for(i=0; i<10; i++) { // upto 10 tries
13351              f = fopen(filename, append ? "a" : "w");
13352              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13353              if(f || errno != 13) break;
13354              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13355              tot += t;
13356         }
13357         if (f == NULL) {
13358             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13359             DisplayError(buf, errno);
13360             return FALSE;
13361         } else {
13362             safeStrCpy(buf, lastMsg, MSG_SIZ);
13363             DisplayMessage(_("Waiting for access to save file"), "");
13364             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13365             DisplayMessage(_("Saving game"), "");
13366             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13367             result = SaveGame(f, 0, NULL);
13368             DisplayMessage(buf, "");
13369             return result;
13370         }
13371     }
13372 }
13373
13374 char *
13375 SavePart (char *str)
13376 {
13377     static char buf[MSG_SIZ];
13378     char *p;
13379
13380     p = strchr(str, ' ');
13381     if (p == NULL) return str;
13382     strncpy(buf, str, p - str);
13383     buf[p - str] = NULLCHAR;
13384     return buf;
13385 }
13386
13387 #define PGN_MAX_LINE 75
13388
13389 #define PGN_SIDE_WHITE  0
13390 #define PGN_SIDE_BLACK  1
13391
13392 static int
13393 FindFirstMoveOutOfBook (int side)
13394 {
13395     int result = -1;
13396
13397     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13398         int index = backwardMostMove;
13399         int has_book_hit = 0;
13400
13401         if( (index % 2) != side ) {
13402             index++;
13403         }
13404
13405         while( index < forwardMostMove ) {
13406             /* Check to see if engine is in book */
13407             int depth = pvInfoList[index].depth;
13408             int score = pvInfoList[index].score;
13409             int in_book = 0;
13410
13411             if( depth <= 2 ) {
13412                 in_book = 1;
13413             }
13414             else if( score == 0 && depth == 63 ) {
13415                 in_book = 1; /* Zappa */
13416             }
13417             else if( score == 2 && depth == 99 ) {
13418                 in_book = 1; /* Abrok */
13419             }
13420
13421             has_book_hit += in_book;
13422
13423             if( ! in_book ) {
13424                 result = index;
13425
13426                 break;
13427             }
13428
13429             index += 2;
13430         }
13431     }
13432
13433     return result;
13434 }
13435
13436 void
13437 GetOutOfBookInfo (char * buf)
13438 {
13439     int oob[2];
13440     int i;
13441     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13442
13443     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13444     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13445
13446     *buf = '\0';
13447
13448     if( oob[0] >= 0 || oob[1] >= 0 ) {
13449         for( i=0; i<2; i++ ) {
13450             int idx = oob[i];
13451
13452             if( idx >= 0 ) {
13453                 if( i > 0 && oob[0] >= 0 ) {
13454                     strcat( buf, "   " );
13455                 }
13456
13457                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13458                 sprintf( buf+strlen(buf), "%s%.2f",
13459                     pvInfoList[idx].score >= 0 ? "+" : "",
13460                     pvInfoList[idx].score / 100.0 );
13461             }
13462         }
13463     }
13464 }
13465
13466 /* Save game in PGN style */
13467 static void
13468 SaveGamePGN2 (FILE *f)
13469 {
13470     int i, offset, linelen, newblock;
13471 //    char *movetext;
13472     char numtext[32];
13473     int movelen, numlen, blank;
13474     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13475
13476     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13477
13478     PrintPGNTags(f, &gameInfo);
13479
13480     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13481
13482     if (backwardMostMove > 0 || startedFromSetupPosition) {
13483         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13484         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13485         fprintf(f, "\n{--------------\n");
13486         PrintPosition(f, backwardMostMove);
13487         fprintf(f, "--------------}\n");
13488         free(fen);
13489     }
13490     else {
13491         /* [AS] Out of book annotation */
13492         if( appData.saveOutOfBookInfo ) {
13493             char buf[64];
13494
13495             GetOutOfBookInfo( buf );
13496
13497             if( buf[0] != '\0' ) {
13498                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13499             }
13500         }
13501
13502         fprintf(f, "\n");
13503     }
13504
13505     i = backwardMostMove;
13506     linelen = 0;
13507     newblock = TRUE;
13508
13509     while (i < forwardMostMove) {
13510         /* Print comments preceding this move */
13511         if (commentList[i] != NULL) {
13512             if (linelen > 0) fprintf(f, "\n");
13513             fprintf(f, "%s", commentList[i]);
13514             linelen = 0;
13515             newblock = TRUE;
13516         }
13517
13518         /* Format move number */
13519         if ((i % 2) == 0)
13520           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13521         else
13522           if (newblock)
13523             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13524           else
13525             numtext[0] = NULLCHAR;
13526
13527         numlen = strlen(numtext);
13528         newblock = FALSE;
13529
13530         /* Print move number */
13531         blank = linelen > 0 && numlen > 0;
13532         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13533             fprintf(f, "\n");
13534             linelen = 0;
13535             blank = 0;
13536         }
13537         if (blank) {
13538             fprintf(f, " ");
13539             linelen++;
13540         }
13541         fprintf(f, "%s", numtext);
13542         linelen += numlen;
13543
13544         /* Get move */
13545         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13546         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13547
13548         /* Print move */
13549         blank = linelen > 0 && movelen > 0;
13550         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13551             fprintf(f, "\n");
13552             linelen = 0;
13553             blank = 0;
13554         }
13555         if (blank) {
13556             fprintf(f, " ");
13557             linelen++;
13558         }
13559         fprintf(f, "%s", move_buffer);
13560         linelen += movelen;
13561
13562         /* [AS] Add PV info if present */
13563         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13564             /* [HGM] add time */
13565             char buf[MSG_SIZ]; int seconds;
13566
13567             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13568
13569             if( seconds <= 0)
13570               buf[0] = 0;
13571             else
13572               if( seconds < 30 )
13573                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13574               else
13575                 {
13576                   seconds = (seconds + 4)/10; // round to full seconds
13577                   if( seconds < 60 )
13578                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13579                   else
13580                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13581                 }
13582
13583             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13584                       pvInfoList[i].score >= 0 ? "+" : "",
13585                       pvInfoList[i].score / 100.0,
13586                       pvInfoList[i].depth,
13587                       buf );
13588
13589             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13590
13591             /* Print score/depth */
13592             blank = linelen > 0 && movelen > 0;
13593             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13594                 fprintf(f, "\n");
13595                 linelen = 0;
13596                 blank = 0;
13597             }
13598             if (blank) {
13599                 fprintf(f, " ");
13600                 linelen++;
13601             }
13602             fprintf(f, "%s", move_buffer);
13603             linelen += movelen;
13604         }
13605
13606         i++;
13607     }
13608
13609     /* Start a new line */
13610     if (linelen > 0) fprintf(f, "\n");
13611
13612     /* Print comments after last move */
13613     if (commentList[i] != NULL) {
13614         fprintf(f, "%s\n", commentList[i]);
13615     }
13616
13617     /* Print result */
13618     if (gameInfo.resultDetails != NULL &&
13619         gameInfo.resultDetails[0] != NULLCHAR) {
13620         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13621         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13622            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13623             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13624         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13625     } else {
13626         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13627     }
13628 }
13629
13630 /* Save game in PGN style and close the file */
13631 int
13632 SaveGamePGN (FILE *f)
13633 {
13634     SaveGamePGN2(f);
13635     fclose(f);
13636     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13637     return TRUE;
13638 }
13639
13640 /* Save game in old style and close the file */
13641 int
13642 SaveGameOldStyle (FILE *f)
13643 {
13644     int i, offset;
13645     time_t tm;
13646
13647     tm = time((time_t *) NULL);
13648
13649     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13650     PrintOpponents(f);
13651
13652     if (backwardMostMove > 0 || startedFromSetupPosition) {
13653         fprintf(f, "\n[--------------\n");
13654         PrintPosition(f, backwardMostMove);
13655         fprintf(f, "--------------]\n");
13656     } else {
13657         fprintf(f, "\n");
13658     }
13659
13660     i = backwardMostMove;
13661     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13662
13663     while (i < forwardMostMove) {
13664         if (commentList[i] != NULL) {
13665             fprintf(f, "[%s]\n", commentList[i]);
13666         }
13667
13668         if ((i % 2) == 1) {
13669             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13670             i++;
13671         } else {
13672             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13673             i++;
13674             if (commentList[i] != NULL) {
13675                 fprintf(f, "\n");
13676                 continue;
13677             }
13678             if (i >= forwardMostMove) {
13679                 fprintf(f, "\n");
13680                 break;
13681             }
13682             fprintf(f, "%s\n", parseList[i]);
13683             i++;
13684         }
13685     }
13686
13687     if (commentList[i] != NULL) {
13688         fprintf(f, "[%s]\n", commentList[i]);
13689     }
13690
13691     /* This isn't really the old style, but it's close enough */
13692     if (gameInfo.resultDetails != NULL &&
13693         gameInfo.resultDetails[0] != NULLCHAR) {
13694         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13695                 gameInfo.resultDetails);
13696     } else {
13697         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13698     }
13699
13700     fclose(f);
13701     return TRUE;
13702 }
13703
13704 /* Save the current game to open file f and close the file */
13705 int
13706 SaveGame (FILE *f, int dummy, char *dummy2)
13707 {
13708     if (gameMode == EditPosition) EditPositionDone(TRUE);
13709     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13710     if (appData.oldSaveStyle)
13711       return SaveGameOldStyle(f);
13712     else
13713       return SaveGamePGN(f);
13714 }
13715
13716 /* Save the current position to the given file */
13717 int
13718 SavePositionToFile (char *filename)
13719 {
13720     FILE *f;
13721     char buf[MSG_SIZ];
13722
13723     if (strcmp(filename, "-") == 0) {
13724         return SavePosition(stdout, 0, NULL);
13725     } else {
13726         f = fopen(filename, "a");
13727         if (f == NULL) {
13728             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13729             DisplayError(buf, errno);
13730             return FALSE;
13731         } else {
13732             safeStrCpy(buf, lastMsg, MSG_SIZ);
13733             DisplayMessage(_("Waiting for access to save file"), "");
13734             flock(fileno(f), LOCK_EX); // [HGM] lock
13735             DisplayMessage(_("Saving position"), "");
13736             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13737             SavePosition(f, 0, NULL);
13738             DisplayMessage(buf, "");
13739             return TRUE;
13740         }
13741     }
13742 }
13743
13744 /* Save the current position to the given open file and close the file */
13745 int
13746 SavePosition (FILE *f, int dummy, char *dummy2)
13747 {
13748     time_t tm;
13749     char *fen;
13750
13751     if (gameMode == EditPosition) EditPositionDone(TRUE);
13752     if (appData.oldSaveStyle) {
13753         tm = time((time_t *) NULL);
13754
13755         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13756         PrintOpponents(f);
13757         fprintf(f, "[--------------\n");
13758         PrintPosition(f, currentMove);
13759         fprintf(f, "--------------]\n");
13760     } else {
13761         fen = PositionToFEN(currentMove, NULL, 1);
13762         fprintf(f, "%s\n", fen);
13763         free(fen);
13764     }
13765     fclose(f);
13766     return TRUE;
13767 }
13768
13769 void
13770 ReloadCmailMsgEvent (int unregister)
13771 {
13772 #if !WIN32
13773     static char *inFilename = NULL;
13774     static char *outFilename;
13775     int i;
13776     struct stat inbuf, outbuf;
13777     int status;
13778
13779     /* Any registered moves are unregistered if unregister is set, */
13780     /* i.e. invoked by the signal handler */
13781     if (unregister) {
13782         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13783             cmailMoveRegistered[i] = FALSE;
13784             if (cmailCommentList[i] != NULL) {
13785                 free(cmailCommentList[i]);
13786                 cmailCommentList[i] = NULL;
13787             }
13788         }
13789         nCmailMovesRegistered = 0;
13790     }
13791
13792     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13793         cmailResult[i] = CMAIL_NOT_RESULT;
13794     }
13795     nCmailResults = 0;
13796
13797     if (inFilename == NULL) {
13798         /* Because the filenames are static they only get malloced once  */
13799         /* and they never get freed                                      */
13800         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13801         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13802
13803         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13804         sprintf(outFilename, "%s.out", appData.cmailGameName);
13805     }
13806
13807     status = stat(outFilename, &outbuf);
13808     if (status < 0) {
13809         cmailMailedMove = FALSE;
13810     } else {
13811         status = stat(inFilename, &inbuf);
13812         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13813     }
13814
13815     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13816        counts the games, notes how each one terminated, etc.
13817
13818        It would be nice to remove this kludge and instead gather all
13819        the information while building the game list.  (And to keep it
13820        in the game list nodes instead of having a bunch of fixed-size
13821        parallel arrays.)  Note this will require getting each game's
13822        termination from the PGN tags, as the game list builder does
13823        not process the game moves.  --mann
13824        */
13825     cmailMsgLoaded = TRUE;
13826     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13827
13828     /* Load first game in the file or popup game menu */
13829     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13830
13831 #endif /* !WIN32 */
13832     return;
13833 }
13834
13835 int
13836 RegisterMove ()
13837 {
13838     FILE *f;
13839     char string[MSG_SIZ];
13840
13841     if (   cmailMailedMove
13842         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13843         return TRUE;            /* Allow free viewing  */
13844     }
13845
13846     /* Unregister move to ensure that we don't leave RegisterMove        */
13847     /* with the move registered when the conditions for registering no   */
13848     /* longer hold                                                       */
13849     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13850         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13851         nCmailMovesRegistered --;
13852
13853         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13854           {
13855               free(cmailCommentList[lastLoadGameNumber - 1]);
13856               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13857           }
13858     }
13859
13860     if (cmailOldMove == -1) {
13861         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13862         return FALSE;
13863     }
13864
13865     if (currentMove > cmailOldMove + 1) {
13866         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13867         return FALSE;
13868     }
13869
13870     if (currentMove < cmailOldMove) {
13871         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13872         return FALSE;
13873     }
13874
13875     if (forwardMostMove > currentMove) {
13876         /* Silently truncate extra moves */
13877         TruncateGame();
13878     }
13879
13880     if (   (currentMove == cmailOldMove + 1)
13881         || (   (currentMove == cmailOldMove)
13882             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13883                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13884         if (gameInfo.result != GameUnfinished) {
13885             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13886         }
13887
13888         if (commentList[currentMove] != NULL) {
13889             cmailCommentList[lastLoadGameNumber - 1]
13890               = StrSave(commentList[currentMove]);
13891         }
13892         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13893
13894         if (appData.debugMode)
13895           fprintf(debugFP, "Saving %s for game %d\n",
13896                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13897
13898         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13899
13900         f = fopen(string, "w");
13901         if (appData.oldSaveStyle) {
13902             SaveGameOldStyle(f); /* also closes the file */
13903
13904             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13905             f = fopen(string, "w");
13906             SavePosition(f, 0, NULL); /* also closes the file */
13907         } else {
13908             fprintf(f, "{--------------\n");
13909             PrintPosition(f, currentMove);
13910             fprintf(f, "--------------}\n\n");
13911
13912             SaveGame(f, 0, NULL); /* also closes the file*/
13913         }
13914
13915         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13916         nCmailMovesRegistered ++;
13917     } else if (nCmailGames == 1) {
13918         DisplayError(_("You have not made a move yet"), 0);
13919         return FALSE;
13920     }
13921
13922     return TRUE;
13923 }
13924
13925 void
13926 MailMoveEvent ()
13927 {
13928 #if !WIN32
13929     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13930     FILE *commandOutput;
13931     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13932     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13933     int nBuffers;
13934     int i;
13935     int archived;
13936     char *arcDir;
13937
13938     if (! cmailMsgLoaded) {
13939         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13940         return;
13941     }
13942
13943     if (nCmailGames == nCmailResults) {
13944         DisplayError(_("No unfinished games"), 0);
13945         return;
13946     }
13947
13948 #if CMAIL_PROHIBIT_REMAIL
13949     if (cmailMailedMove) {
13950       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);
13951         DisplayError(msg, 0);
13952         return;
13953     }
13954 #endif
13955
13956     if (! (cmailMailedMove || RegisterMove())) return;
13957
13958     if (   cmailMailedMove
13959         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13960       snprintf(string, MSG_SIZ, partCommandString,
13961                appData.debugMode ? " -v" : "", appData.cmailGameName);
13962         commandOutput = popen(string, "r");
13963
13964         if (commandOutput == NULL) {
13965             DisplayError(_("Failed to invoke cmail"), 0);
13966         } else {
13967             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13968                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13969             }
13970             if (nBuffers > 1) {
13971                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13972                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13973                 nBytes = MSG_SIZ - 1;
13974             } else {
13975                 (void) memcpy(msg, buffer, nBytes);
13976             }
13977             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13978
13979             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13980                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13981
13982                 archived = TRUE;
13983                 for (i = 0; i < nCmailGames; i ++) {
13984                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13985                         archived = FALSE;
13986                     }
13987                 }
13988                 if (   archived
13989                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13990                         != NULL)) {
13991                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13992                            arcDir,
13993                            appData.cmailGameName,
13994                            gameInfo.date);
13995                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13996                     cmailMsgLoaded = FALSE;
13997                 }
13998             }
13999
14000             DisplayInformation(msg);
14001             pclose(commandOutput);
14002         }
14003     } else {
14004         if ((*cmailMsg) != '\0') {
14005             DisplayInformation(cmailMsg);
14006         }
14007     }
14008
14009     return;
14010 #endif /* !WIN32 */
14011 }
14012
14013 char *
14014 CmailMsg ()
14015 {
14016 #if WIN32
14017     return NULL;
14018 #else
14019     int  prependComma = 0;
14020     char number[5];
14021     char string[MSG_SIZ];       /* Space for game-list */
14022     int  i;
14023
14024     if (!cmailMsgLoaded) return "";
14025
14026     if (cmailMailedMove) {
14027       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14028     } else {
14029         /* Create a list of games left */
14030       snprintf(string, MSG_SIZ, "[");
14031         for (i = 0; i < nCmailGames; i ++) {
14032             if (! (   cmailMoveRegistered[i]
14033                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14034                 if (prependComma) {
14035                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14036                 } else {
14037                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14038                     prependComma = 1;
14039                 }
14040
14041                 strcat(string, number);
14042             }
14043         }
14044         strcat(string, "]");
14045
14046         if (nCmailMovesRegistered + nCmailResults == 0) {
14047             switch (nCmailGames) {
14048               case 1:
14049                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14050                 break;
14051
14052               case 2:
14053                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14054                 break;
14055
14056               default:
14057                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14058                          nCmailGames);
14059                 break;
14060             }
14061         } else {
14062             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14063               case 1:
14064                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14065                          string);
14066                 break;
14067
14068               case 0:
14069                 if (nCmailResults == nCmailGames) {
14070                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14071                 } else {
14072                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14073                 }
14074                 break;
14075
14076               default:
14077                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14078                          string);
14079             }
14080         }
14081     }
14082     return cmailMsg;
14083 #endif /* WIN32 */
14084 }
14085
14086 void
14087 ResetGameEvent ()
14088 {
14089     if (gameMode == Training)
14090       SetTrainingModeOff();
14091
14092     Reset(TRUE, TRUE);
14093     cmailMsgLoaded = FALSE;
14094     if (appData.icsActive) {
14095       SendToICS(ics_prefix);
14096       SendToICS("refresh\n");
14097     }
14098 }
14099
14100 void
14101 ExitEvent (int status)
14102 {
14103     exiting++;
14104     if (exiting > 2) {
14105       /* Give up on clean exit */
14106       exit(status);
14107     }
14108     if (exiting > 1) {
14109       /* Keep trying for clean exit */
14110       return;
14111     }
14112
14113     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14114     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14115
14116     if (telnetISR != NULL) {
14117       RemoveInputSource(telnetISR);
14118     }
14119     if (icsPR != NoProc) {
14120       DestroyChildProcess(icsPR, TRUE);
14121     }
14122
14123     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14124     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14125
14126     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14127     /* make sure this other one finishes before killing it!                  */
14128     if(endingGame) { int count = 0;
14129         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14130         while(endingGame && count++ < 10) DoSleep(1);
14131         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14132     }
14133
14134     /* Kill off chess programs */
14135     if (first.pr != NoProc) {
14136         ExitAnalyzeMode();
14137
14138         DoSleep( appData.delayBeforeQuit );
14139         SendToProgram("quit\n", &first);
14140         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14141     }
14142     if (second.pr != NoProc) {
14143         DoSleep( appData.delayBeforeQuit );
14144         SendToProgram("quit\n", &second);
14145         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14146     }
14147     if (first.isr != NULL) {
14148         RemoveInputSource(first.isr);
14149     }
14150     if (second.isr != NULL) {
14151         RemoveInputSource(second.isr);
14152     }
14153
14154     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14155     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14156
14157     ShutDownFrontEnd();
14158     exit(status);
14159 }
14160
14161 void
14162 PauseEngine (ChessProgramState *cps)
14163 {
14164     SendToProgram("pause\n", cps);
14165     cps->pause = 2;
14166 }
14167
14168 void
14169 UnPauseEngine (ChessProgramState *cps)
14170 {
14171     SendToProgram("resume\n", cps);
14172     cps->pause = 1;
14173 }
14174
14175 void
14176 PauseEvent ()
14177 {
14178     if (appData.debugMode)
14179         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14180     if (pausing) {
14181         pausing = FALSE;
14182         ModeHighlight();
14183         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14184             StartClocks();
14185             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14186                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14187                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14188             }
14189             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14190             HandleMachineMove(stashedInputMove, stalledEngine);
14191             stalledEngine = NULL;
14192             return;
14193         }
14194         if (gameMode == MachinePlaysWhite ||
14195             gameMode == TwoMachinesPlay   ||
14196             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14197             if(first.pause)  UnPauseEngine(&first);
14198             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14199             if(second.pause) UnPauseEngine(&second);
14200             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14201             StartClocks();
14202         } else {
14203             DisplayBothClocks();
14204         }
14205         if (gameMode == PlayFromGameFile) {
14206             if (appData.timeDelay >= 0)
14207                 AutoPlayGameLoop();
14208         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14209             Reset(FALSE, TRUE);
14210             SendToICS(ics_prefix);
14211             SendToICS("refresh\n");
14212         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14213             ForwardInner(forwardMostMove);
14214         }
14215         pauseExamInvalid = FALSE;
14216     } else {
14217         switch (gameMode) {
14218           default:
14219             return;
14220           case IcsExamining:
14221             pauseExamForwardMostMove = forwardMostMove;
14222             pauseExamInvalid = FALSE;
14223             /* fall through */
14224           case IcsObserving:
14225           case IcsPlayingWhite:
14226           case IcsPlayingBlack:
14227             pausing = TRUE;
14228             ModeHighlight();
14229             return;
14230           case PlayFromGameFile:
14231             (void) StopLoadGameTimer();
14232             pausing = TRUE;
14233             ModeHighlight();
14234             break;
14235           case BeginningOfGame:
14236             if (appData.icsActive) return;
14237             /* else fall through */
14238           case MachinePlaysWhite:
14239           case MachinePlaysBlack:
14240           case TwoMachinesPlay:
14241             if (forwardMostMove == 0)
14242               return;           /* don't pause if no one has moved */
14243             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14244                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14245                 if(onMove->pause) {           // thinking engine can be paused
14246                     PauseEngine(onMove);      // do it
14247                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14248                         PauseEngine(onMove->other);
14249                     else
14250                         SendToProgram("easy\n", onMove->other);
14251                     StopClocks();
14252                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14253             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14254                 if(first.pause) {
14255                     PauseEngine(&first);
14256                     StopClocks();
14257                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14258             } else { // human on move, pause pondering by either method
14259                 if(first.pause)
14260                     PauseEngine(&first);
14261                 else if(appData.ponderNextMove)
14262                     SendToProgram("easy\n", &first);
14263                 StopClocks();
14264             }
14265             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14266           case AnalyzeMode:
14267             pausing = TRUE;
14268             ModeHighlight();
14269             break;
14270         }
14271     }
14272 }
14273
14274 void
14275 EditCommentEvent ()
14276 {
14277     char title[MSG_SIZ];
14278
14279     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14280       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14281     } else {
14282       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14283                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14284                parseList[currentMove - 1]);
14285     }
14286
14287     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14288 }
14289
14290
14291 void
14292 EditTagsEvent ()
14293 {
14294     char *tags = PGNTags(&gameInfo);
14295     bookUp = FALSE;
14296     EditTagsPopUp(tags, NULL);
14297     free(tags);
14298 }
14299
14300 void
14301 ToggleSecond ()
14302 {
14303   if(second.analyzing) {
14304     SendToProgram("exit\n", &second);
14305     second.analyzing = FALSE;
14306   } else {
14307     if (second.pr == NoProc) StartChessProgram(&second);
14308     InitChessProgram(&second, FALSE);
14309     FeedMovesToProgram(&second, currentMove);
14310
14311     SendToProgram("analyze\n", &second);
14312     second.analyzing = TRUE;
14313   }
14314 }
14315
14316 /* Toggle ShowThinking */
14317 void
14318 ToggleShowThinking()
14319 {
14320   appData.showThinking = !appData.showThinking;
14321   ShowThinkingEvent();
14322 }
14323
14324 int
14325 AnalyzeModeEvent ()
14326 {
14327     char buf[MSG_SIZ];
14328
14329     if (!first.analysisSupport) {
14330       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14331       DisplayError(buf, 0);
14332       return 0;
14333     }
14334     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14335     if (appData.icsActive) {
14336         if (gameMode != IcsObserving) {
14337           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14338             DisplayError(buf, 0);
14339             /* secure check */
14340             if (appData.icsEngineAnalyze) {
14341                 if (appData.debugMode)
14342                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14343                 ExitAnalyzeMode();
14344                 ModeHighlight();
14345             }
14346             return 0;
14347         }
14348         /* if enable, user wants to disable icsEngineAnalyze */
14349         if (appData.icsEngineAnalyze) {
14350                 ExitAnalyzeMode();
14351                 ModeHighlight();
14352                 return 0;
14353         }
14354         appData.icsEngineAnalyze = TRUE;
14355         if (appData.debugMode)
14356             fprintf(debugFP, "ICS engine analyze starting... \n");
14357     }
14358
14359     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14360     if (appData.noChessProgram || gameMode == AnalyzeMode)
14361       return 0;
14362
14363     if (gameMode != AnalyzeFile) {
14364         if (!appData.icsEngineAnalyze) {
14365                EditGameEvent();
14366                if (gameMode != EditGame) return 0;
14367         }
14368         if (!appData.showThinking) ToggleShowThinking();
14369         ResurrectChessProgram();
14370         SendToProgram("analyze\n", &first);
14371         first.analyzing = TRUE;
14372         /*first.maybeThinking = TRUE;*/
14373         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14374         EngineOutputPopUp();
14375     }
14376     if (!appData.icsEngineAnalyze) {
14377         gameMode = AnalyzeMode;
14378         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14379     }
14380     pausing = FALSE;
14381     ModeHighlight();
14382     SetGameInfo();
14383
14384     StartAnalysisClock();
14385     GetTimeMark(&lastNodeCountTime);
14386     lastNodeCount = 0;
14387     return 1;
14388 }
14389
14390 void
14391 AnalyzeFileEvent ()
14392 {
14393     if (appData.noChessProgram || gameMode == AnalyzeFile)
14394       return;
14395
14396     if (!first.analysisSupport) {
14397       char buf[MSG_SIZ];
14398       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14399       DisplayError(buf, 0);
14400       return;
14401     }
14402
14403     if (gameMode != AnalyzeMode) {
14404         keepInfo = 1; // mere annotating should not alter PGN tags
14405         EditGameEvent();
14406         keepInfo = 0;
14407         if (gameMode != EditGame) return;
14408         if (!appData.showThinking) ToggleShowThinking();
14409         ResurrectChessProgram();
14410         SendToProgram("analyze\n", &first);
14411         first.analyzing = TRUE;
14412         /*first.maybeThinking = TRUE;*/
14413         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14414         EngineOutputPopUp();
14415     }
14416     gameMode = AnalyzeFile;
14417     pausing = FALSE;
14418     ModeHighlight();
14419
14420     StartAnalysisClock();
14421     GetTimeMark(&lastNodeCountTime);
14422     lastNodeCount = 0;
14423     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14424     AnalysisPeriodicEvent(1);
14425 }
14426
14427 void
14428 MachineWhiteEvent ()
14429 {
14430     char buf[MSG_SIZ];
14431     char *bookHit = NULL;
14432
14433     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14434       return;
14435
14436
14437     if (gameMode == PlayFromGameFile ||
14438         gameMode == TwoMachinesPlay  ||
14439         gameMode == Training         ||
14440         gameMode == AnalyzeMode      ||
14441         gameMode == EndOfGame)
14442         EditGameEvent();
14443
14444     if (gameMode == EditPosition)
14445         EditPositionDone(TRUE);
14446
14447     if (!WhiteOnMove(currentMove)) {
14448         DisplayError(_("It is not White's turn"), 0);
14449         return;
14450     }
14451
14452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14453       ExitAnalyzeMode();
14454
14455     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14456         gameMode == AnalyzeFile)
14457         TruncateGame();
14458
14459     ResurrectChessProgram();    /* in case it isn't running */
14460     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14461         gameMode = MachinePlaysWhite;
14462         ResetClocks();
14463     } else
14464     gameMode = MachinePlaysWhite;
14465     pausing = FALSE;
14466     ModeHighlight();
14467     SetGameInfo();
14468     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14469     DisplayTitle(buf);
14470     if (first.sendName) {
14471       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14472       SendToProgram(buf, &first);
14473     }
14474     if (first.sendTime) {
14475       if (first.useColors) {
14476         SendToProgram("black\n", &first); /*gnu kludge*/
14477       }
14478       SendTimeRemaining(&first, TRUE);
14479     }
14480     if (first.useColors) {
14481       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14482     }
14483     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14484     SetMachineThinkingEnables();
14485     first.maybeThinking = TRUE;
14486     StartClocks();
14487     firstMove = FALSE;
14488
14489     if (appData.autoFlipView && !flipView) {
14490       flipView = !flipView;
14491       DrawPosition(FALSE, NULL);
14492       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14493     }
14494
14495     if(bookHit) { // [HGM] book: simulate book reply
14496         static char bookMove[MSG_SIZ]; // a bit generous?
14497
14498         programStats.nodes = programStats.depth = programStats.time =
14499         programStats.score = programStats.got_only_move = 0;
14500         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14501
14502         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14503         strcat(bookMove, bookHit);
14504         HandleMachineMove(bookMove, &first);
14505     }
14506 }
14507
14508 void
14509 MachineBlackEvent ()
14510 {
14511   char buf[MSG_SIZ];
14512   char *bookHit = NULL;
14513
14514     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14515         return;
14516
14517
14518     if (gameMode == PlayFromGameFile ||
14519         gameMode == TwoMachinesPlay  ||
14520         gameMode == Training         ||
14521         gameMode == AnalyzeMode      ||
14522         gameMode == EndOfGame)
14523         EditGameEvent();
14524
14525     if (gameMode == EditPosition)
14526         EditPositionDone(TRUE);
14527
14528     if (WhiteOnMove(currentMove)) {
14529         DisplayError(_("It is not Black's turn"), 0);
14530         return;
14531     }
14532
14533     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14534       ExitAnalyzeMode();
14535
14536     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14537         gameMode == AnalyzeFile)
14538         TruncateGame();
14539
14540     ResurrectChessProgram();    /* in case it isn't running */
14541     gameMode = MachinePlaysBlack;
14542     pausing = FALSE;
14543     ModeHighlight();
14544     SetGameInfo();
14545     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14546     DisplayTitle(buf);
14547     if (first.sendName) {
14548       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14549       SendToProgram(buf, &first);
14550     }
14551     if (first.sendTime) {
14552       if (first.useColors) {
14553         SendToProgram("white\n", &first); /*gnu kludge*/
14554       }
14555       SendTimeRemaining(&first, FALSE);
14556     }
14557     if (first.useColors) {
14558       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14559     }
14560     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14561     SetMachineThinkingEnables();
14562     first.maybeThinking = TRUE;
14563     StartClocks();
14564
14565     if (appData.autoFlipView && flipView) {
14566       flipView = !flipView;
14567       DrawPosition(FALSE, NULL);
14568       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14569     }
14570     if(bookHit) { // [HGM] book: simulate book reply
14571         static char bookMove[MSG_SIZ]; // a bit generous?
14572
14573         programStats.nodes = programStats.depth = programStats.time =
14574         programStats.score = programStats.got_only_move = 0;
14575         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14576
14577         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14578         strcat(bookMove, bookHit);
14579         HandleMachineMove(bookMove, &first);
14580     }
14581 }
14582
14583
14584 void
14585 DisplayTwoMachinesTitle ()
14586 {
14587     char buf[MSG_SIZ];
14588     if (appData.matchGames > 0) {
14589         if(appData.tourneyFile[0]) {
14590           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14591                    gameInfo.white, _("vs."), gameInfo.black,
14592                    nextGame+1, appData.matchGames+1,
14593                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14594         } else
14595         if (first.twoMachinesColor[0] == 'w') {
14596           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14597                    gameInfo.white, _("vs."),  gameInfo.black,
14598                    first.matchWins, second.matchWins,
14599                    matchGame - 1 - (first.matchWins + second.matchWins));
14600         } else {
14601           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14602                    gameInfo.white, _("vs."), gameInfo.black,
14603                    second.matchWins, first.matchWins,
14604                    matchGame - 1 - (first.matchWins + second.matchWins));
14605         }
14606     } else {
14607       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14608     }
14609     DisplayTitle(buf);
14610 }
14611
14612 void
14613 SettingsMenuIfReady ()
14614 {
14615   if (second.lastPing != second.lastPong) {
14616     DisplayMessage("", _("Waiting for second chess program"));
14617     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14618     return;
14619   }
14620   ThawUI();
14621   DisplayMessage("", "");
14622   SettingsPopUp(&second);
14623 }
14624
14625 int
14626 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14627 {
14628     char buf[MSG_SIZ];
14629     if (cps->pr == NoProc) {
14630         StartChessProgram(cps);
14631         if (cps->protocolVersion == 1) {
14632           retry();
14633           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14634         } else {
14635           /* kludge: allow timeout for initial "feature" command */
14636           if(retry != TwoMachinesEventIfReady) FreezeUI();
14637           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14638           DisplayMessage("", buf);
14639           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14640         }
14641         return 1;
14642     }
14643     return 0;
14644 }
14645
14646 void
14647 TwoMachinesEvent P((void))
14648 {
14649     int i;
14650     char buf[MSG_SIZ];
14651     ChessProgramState *onmove;
14652     char *bookHit = NULL;
14653     static int stalling = 0;
14654     TimeMark now;
14655     long wait;
14656
14657     if (appData.noChessProgram) return;
14658
14659     switch (gameMode) {
14660       case TwoMachinesPlay:
14661         return;
14662       case MachinePlaysWhite:
14663       case MachinePlaysBlack:
14664         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14665             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14666             return;
14667         }
14668         /* fall through */
14669       case BeginningOfGame:
14670       case PlayFromGameFile:
14671       case EndOfGame:
14672         EditGameEvent();
14673         if (gameMode != EditGame) return;
14674         break;
14675       case EditPosition:
14676         EditPositionDone(TRUE);
14677         break;
14678       case AnalyzeMode:
14679       case AnalyzeFile:
14680         ExitAnalyzeMode();
14681         break;
14682       case EditGame:
14683       default:
14684         break;
14685     }
14686
14687 //    forwardMostMove = currentMove;
14688     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14689     startingEngine = TRUE;
14690
14691     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14692
14693     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14694     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14695       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14696       return;
14697     }
14698     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14699
14700     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14701                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14702         startingEngine = matchMode = FALSE;
14703         DisplayError("second engine does not play this", 0);
14704         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14705         EditGameEvent(); // switch back to EditGame mode
14706         return;
14707     }
14708
14709     if(!stalling) {
14710       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14711       SendToProgram("force\n", &second);
14712       stalling = 1;
14713       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14714       return;
14715     }
14716     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14717     if(appData.matchPause>10000 || appData.matchPause<10)
14718                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14719     wait = SubtractTimeMarks(&now, &pauseStart);
14720     if(wait < appData.matchPause) {
14721         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14722         return;
14723     }
14724     // we are now committed to starting the game
14725     stalling = 0;
14726     DisplayMessage("", "");
14727     if (startedFromSetupPosition) {
14728         SendBoard(&second, backwardMostMove);
14729     if (appData.debugMode) {
14730         fprintf(debugFP, "Two Machines\n");
14731     }
14732     }
14733     for (i = backwardMostMove; i < forwardMostMove; i++) {
14734         SendMoveToProgram(i, &second);
14735     }
14736
14737     gameMode = TwoMachinesPlay;
14738     pausing = startingEngine = FALSE;
14739     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14740     SetGameInfo();
14741     DisplayTwoMachinesTitle();
14742     firstMove = TRUE;
14743     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14744         onmove = &first;
14745     } else {
14746         onmove = &second;
14747     }
14748     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14749     SendToProgram(first.computerString, &first);
14750     if (first.sendName) {
14751       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14752       SendToProgram(buf, &first);
14753     }
14754     SendToProgram(second.computerString, &second);
14755     if (second.sendName) {
14756       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14757       SendToProgram(buf, &second);
14758     }
14759
14760     ResetClocks();
14761     if (!first.sendTime || !second.sendTime) {
14762         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14763         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14764     }
14765     if (onmove->sendTime) {
14766       if (onmove->useColors) {
14767         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14768       }
14769       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14770     }
14771     if (onmove->useColors) {
14772       SendToProgram(onmove->twoMachinesColor, onmove);
14773     }
14774     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14775 //    SendToProgram("go\n", onmove);
14776     onmove->maybeThinking = TRUE;
14777     SetMachineThinkingEnables();
14778
14779     StartClocks();
14780
14781     if(bookHit) { // [HGM] book: simulate book reply
14782         static char bookMove[MSG_SIZ]; // a bit generous?
14783
14784         programStats.nodes = programStats.depth = programStats.time =
14785         programStats.score = programStats.got_only_move = 0;
14786         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14787
14788         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14789         strcat(bookMove, bookHit);
14790         savedMessage = bookMove; // args for deferred call
14791         savedState = onmove;
14792         ScheduleDelayedEvent(DeferredBookMove, 1);
14793     }
14794 }
14795
14796 void
14797 TrainingEvent ()
14798 {
14799     if (gameMode == Training) {
14800       SetTrainingModeOff();
14801       gameMode = PlayFromGameFile;
14802       DisplayMessage("", _("Training mode off"));
14803     } else {
14804       gameMode = Training;
14805       animateTraining = appData.animate;
14806
14807       /* make sure we are not already at the end of the game */
14808       if (currentMove < forwardMostMove) {
14809         SetTrainingModeOn();
14810         DisplayMessage("", _("Training mode on"));
14811       } else {
14812         gameMode = PlayFromGameFile;
14813         DisplayError(_("Already at end of game"), 0);
14814       }
14815     }
14816     ModeHighlight();
14817 }
14818
14819 void
14820 IcsClientEvent ()
14821 {
14822     if (!appData.icsActive) return;
14823     switch (gameMode) {
14824       case IcsPlayingWhite:
14825       case IcsPlayingBlack:
14826       case IcsObserving:
14827       case IcsIdle:
14828       case BeginningOfGame:
14829       case IcsExamining:
14830         return;
14831
14832       case EditGame:
14833         break;
14834
14835       case EditPosition:
14836         EditPositionDone(TRUE);
14837         break;
14838
14839       case AnalyzeMode:
14840       case AnalyzeFile:
14841         ExitAnalyzeMode();
14842         break;
14843
14844       default:
14845         EditGameEvent();
14846         break;
14847     }
14848
14849     gameMode = IcsIdle;
14850     ModeHighlight();
14851     return;
14852 }
14853
14854 void
14855 EditGameEvent ()
14856 {
14857     int i;
14858
14859     switch (gameMode) {
14860       case Training:
14861         SetTrainingModeOff();
14862         break;
14863       case MachinePlaysWhite:
14864       case MachinePlaysBlack:
14865       case BeginningOfGame:
14866         SendToProgram("force\n", &first);
14867         SetUserThinkingEnables();
14868         break;
14869       case PlayFromGameFile:
14870         (void) StopLoadGameTimer();
14871         if (gameFileFP != NULL) {
14872             gameFileFP = NULL;
14873         }
14874         break;
14875       case EditPosition:
14876         EditPositionDone(TRUE);
14877         break;
14878       case AnalyzeMode:
14879       case AnalyzeFile:
14880         ExitAnalyzeMode();
14881         SendToProgram("force\n", &first);
14882         break;
14883       case TwoMachinesPlay:
14884         GameEnds(EndOfFile, NULL, GE_PLAYER);
14885         ResurrectChessProgram();
14886         SetUserThinkingEnables();
14887         break;
14888       case EndOfGame:
14889         ResurrectChessProgram();
14890         break;
14891       case IcsPlayingBlack:
14892       case IcsPlayingWhite:
14893         DisplayError(_("Warning: You are still playing a game"), 0);
14894         break;
14895       case IcsObserving:
14896         DisplayError(_("Warning: You are still observing a game"), 0);
14897         break;
14898       case IcsExamining:
14899         DisplayError(_("Warning: You are still examining a game"), 0);
14900         break;
14901       case IcsIdle:
14902         break;
14903       case EditGame:
14904       default:
14905         return;
14906     }
14907
14908     pausing = FALSE;
14909     StopClocks();
14910     first.offeredDraw = second.offeredDraw = 0;
14911
14912     if (gameMode == PlayFromGameFile) {
14913         whiteTimeRemaining = timeRemaining[0][currentMove];
14914         blackTimeRemaining = timeRemaining[1][currentMove];
14915         DisplayTitle("");
14916     }
14917
14918     if (gameMode == MachinePlaysWhite ||
14919         gameMode == MachinePlaysBlack ||
14920         gameMode == TwoMachinesPlay ||
14921         gameMode == EndOfGame) {
14922         i = forwardMostMove;
14923         while (i > currentMove) {
14924             SendToProgram("undo\n", &first);
14925             i--;
14926         }
14927         if(!adjustedClock) {
14928         whiteTimeRemaining = timeRemaining[0][currentMove];
14929         blackTimeRemaining = timeRemaining[1][currentMove];
14930         DisplayBothClocks();
14931         }
14932         if (whiteFlag || blackFlag) {
14933             whiteFlag = blackFlag = 0;
14934         }
14935         DisplayTitle("");
14936     }
14937
14938     gameMode = EditGame;
14939     ModeHighlight();
14940     SetGameInfo();
14941 }
14942
14943
14944 void
14945 EditPositionEvent ()
14946 {
14947     if (gameMode == EditPosition) {
14948         EditGameEvent();
14949         return;
14950     }
14951
14952     EditGameEvent();
14953     if (gameMode != EditGame) return;
14954
14955     gameMode = EditPosition;
14956     ModeHighlight();
14957     SetGameInfo();
14958     if (currentMove > 0)
14959       CopyBoard(boards[0], boards[currentMove]);
14960
14961     blackPlaysFirst = !WhiteOnMove(currentMove);
14962     ResetClocks();
14963     currentMove = forwardMostMove = backwardMostMove = 0;
14964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14965     DisplayMove(-1);
14966     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14967 }
14968
14969 void
14970 ExitAnalyzeMode ()
14971 {
14972     /* [DM] icsEngineAnalyze - possible call from other functions */
14973     if (appData.icsEngineAnalyze) {
14974         appData.icsEngineAnalyze = FALSE;
14975
14976         DisplayMessage("",_("Close ICS engine analyze..."));
14977     }
14978     if (first.analysisSupport && first.analyzing) {
14979       SendToBoth("exit\n");
14980       first.analyzing = second.analyzing = FALSE;
14981     }
14982     thinkOutput[0] = NULLCHAR;
14983 }
14984
14985 void
14986 EditPositionDone (Boolean fakeRights)
14987 {
14988     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14989
14990     startedFromSetupPosition = TRUE;
14991     InitChessProgram(&first, FALSE);
14992     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14993       boards[0][EP_STATUS] = EP_NONE;
14994       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14995       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14996         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14997         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14998       } else boards[0][CASTLING][2] = NoRights;
14999       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15000         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15001         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15002       } else boards[0][CASTLING][5] = NoRights;
15003       if(gameInfo.variant == VariantSChess) {
15004         int i;
15005         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15006           boards[0][VIRGIN][i] = 0;
15007           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15008           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15009         }
15010       }
15011     }
15012     SendToProgram("force\n", &first);
15013     if (blackPlaysFirst) {
15014         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15015         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15016         currentMove = forwardMostMove = backwardMostMove = 1;
15017         CopyBoard(boards[1], boards[0]);
15018     } else {
15019         currentMove = forwardMostMove = backwardMostMove = 0;
15020     }
15021     SendBoard(&first, forwardMostMove);
15022     if (appData.debugMode) {
15023         fprintf(debugFP, "EditPosDone\n");
15024     }
15025     DisplayTitle("");
15026     DisplayMessage("", "");
15027     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15028     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15029     gameMode = EditGame;
15030     ModeHighlight();
15031     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15032     ClearHighlights(); /* [AS] */
15033 }
15034
15035 /* Pause for `ms' milliseconds */
15036 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15037 void
15038 TimeDelay (long ms)
15039 {
15040     TimeMark m1, m2;
15041
15042     GetTimeMark(&m1);
15043     do {
15044         GetTimeMark(&m2);
15045     } while (SubtractTimeMarks(&m2, &m1) < ms);
15046 }
15047
15048 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15049 void
15050 SendMultiLineToICS (char *buf)
15051 {
15052     char temp[MSG_SIZ+1], *p;
15053     int len;
15054
15055     len = strlen(buf);
15056     if (len > MSG_SIZ)
15057       len = MSG_SIZ;
15058
15059     strncpy(temp, buf, len);
15060     temp[len] = 0;
15061
15062     p = temp;
15063     while (*p) {
15064         if (*p == '\n' || *p == '\r')
15065           *p = ' ';
15066         ++p;
15067     }
15068
15069     strcat(temp, "\n");
15070     SendToICS(temp);
15071     SendToPlayer(temp, strlen(temp));
15072 }
15073
15074 void
15075 SetWhiteToPlayEvent ()
15076 {
15077     if (gameMode == EditPosition) {
15078         blackPlaysFirst = FALSE;
15079         DisplayBothClocks();    /* works because currentMove is 0 */
15080     } else if (gameMode == IcsExamining) {
15081         SendToICS(ics_prefix);
15082         SendToICS("tomove white\n");
15083     }
15084 }
15085
15086 void
15087 SetBlackToPlayEvent ()
15088 {
15089     if (gameMode == EditPosition) {
15090         blackPlaysFirst = TRUE;
15091         currentMove = 1;        /* kludge */
15092         DisplayBothClocks();
15093         currentMove = 0;
15094     } else if (gameMode == IcsExamining) {
15095         SendToICS(ics_prefix);
15096         SendToICS("tomove black\n");
15097     }
15098 }
15099
15100 void
15101 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15102 {
15103     char buf[MSG_SIZ];
15104     ChessSquare piece = boards[0][y][x];
15105     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15106     static int lastVariant;
15107
15108     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15109
15110     switch (selection) {
15111       case ClearBoard:
15112         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15113         MarkTargetSquares(1);
15114         CopyBoard(currentBoard, boards[0]);
15115         CopyBoard(menuBoard, initialPosition);
15116         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15117             SendToICS(ics_prefix);
15118             SendToICS("bsetup clear\n");
15119         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15120             SendToICS(ics_prefix);
15121             SendToICS("clearboard\n");
15122         } else {
15123             int nonEmpty = 0;
15124             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15125                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15126                 for (y = 0; y < BOARD_HEIGHT; y++) {
15127                     if (gameMode == IcsExamining) {
15128                         if (boards[currentMove][y][x] != EmptySquare) {
15129                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15130                                     AAA + x, ONE + y);
15131                             SendToICS(buf);
15132                         }
15133                     } else if(boards[0][y][x] != DarkSquare) {
15134                         if(boards[0][y][x] != p) nonEmpty++;
15135                         boards[0][y][x] = p;
15136                     }
15137                 }
15138             }
15139             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15140                 int r;
15141                 for(r = 0; r < BOARD_HEIGHT; r++) {
15142                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15143                     ChessSquare p = menuBoard[r][x];
15144                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15145                   }
15146                 }
15147                 DisplayMessage("Clicking clock again restores position", "");
15148                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15149                 if(!nonEmpty) { // asked to clear an empty board
15150                     CopyBoard(boards[0], menuBoard);
15151                 } else
15152                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15153                     CopyBoard(boards[0], initialPosition);
15154                 } else
15155                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15156                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15157                     CopyBoard(boards[0], erasedBoard);
15158                 } else
15159                     CopyBoard(erasedBoard, currentBoard);
15160
15161             }
15162         }
15163         if (gameMode == EditPosition) {
15164             DrawPosition(FALSE, boards[0]);
15165         }
15166         break;
15167
15168       case WhitePlay:
15169         SetWhiteToPlayEvent();
15170         break;
15171
15172       case BlackPlay:
15173         SetBlackToPlayEvent();
15174         break;
15175
15176       case EmptySquare:
15177         if (gameMode == IcsExamining) {
15178             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15179             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15180             SendToICS(buf);
15181         } else {
15182             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15183                 if(x == BOARD_LEFT-2) {
15184                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15185                     boards[0][y][1] = 0;
15186                 } else
15187                 if(x == BOARD_RGHT+1) {
15188                     if(y >= gameInfo.holdingsSize) break;
15189                     boards[0][y][BOARD_WIDTH-2] = 0;
15190                 } else break;
15191             }
15192             boards[0][y][x] = EmptySquare;
15193             DrawPosition(FALSE, boards[0]);
15194         }
15195         break;
15196
15197       case PromotePiece:
15198         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15199            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15200             selection = (ChessSquare) (PROMOTED piece);
15201         } else if(piece == EmptySquare) selection = WhiteSilver;
15202         else selection = (ChessSquare)((int)piece - 1);
15203         goto defaultlabel;
15204
15205       case DemotePiece:
15206         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15207            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15208             selection = (ChessSquare) (DEMOTED piece);
15209         } else if(piece == EmptySquare) selection = BlackSilver;
15210         else selection = (ChessSquare)((int)piece + 1);
15211         goto defaultlabel;
15212
15213       case WhiteQueen:
15214       case BlackQueen:
15215         if(gameInfo.variant == VariantShatranj ||
15216            gameInfo.variant == VariantXiangqi  ||
15217            gameInfo.variant == VariantCourier  ||
15218            gameInfo.variant == VariantASEAN    ||
15219            gameInfo.variant == VariantMakruk     )
15220             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15221         goto defaultlabel;
15222
15223       case WhiteKing:
15224       case BlackKing:
15225         if(gameInfo.variant == VariantXiangqi)
15226             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15227         if(gameInfo.variant == VariantKnightmate)
15228             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15229       default:
15230         defaultlabel:
15231         if (gameMode == IcsExamining) {
15232             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15233             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15234                      PieceToChar(selection), AAA + x, ONE + y);
15235             SendToICS(buf);
15236         } else {
15237             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15238                 int n;
15239                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15240                     n = PieceToNumber(selection - BlackPawn);
15241                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15242                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15243                     boards[0][BOARD_HEIGHT-1-n][1]++;
15244                 } else
15245                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15246                     n = PieceToNumber(selection);
15247                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15248                     boards[0][n][BOARD_WIDTH-1] = selection;
15249                     boards[0][n][BOARD_WIDTH-2]++;
15250                 }
15251             } else
15252             boards[0][y][x] = selection;
15253             DrawPosition(TRUE, boards[0]);
15254             ClearHighlights();
15255             fromX = fromY = -1;
15256         }
15257         break;
15258     }
15259 }
15260
15261
15262 void
15263 DropMenuEvent (ChessSquare selection, int x, int y)
15264 {
15265     ChessMove moveType;
15266
15267     switch (gameMode) {
15268       case IcsPlayingWhite:
15269       case MachinePlaysBlack:
15270         if (!WhiteOnMove(currentMove)) {
15271             DisplayMoveError(_("It is Black's turn"));
15272             return;
15273         }
15274         moveType = WhiteDrop;
15275         break;
15276       case IcsPlayingBlack:
15277       case MachinePlaysWhite:
15278         if (WhiteOnMove(currentMove)) {
15279             DisplayMoveError(_("It is White's turn"));
15280             return;
15281         }
15282         moveType = BlackDrop;
15283         break;
15284       case EditGame:
15285         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15286         break;
15287       default:
15288         return;
15289     }
15290
15291     if (moveType == BlackDrop && selection < BlackPawn) {
15292       selection = (ChessSquare) ((int) selection
15293                                  + (int) BlackPawn - (int) WhitePawn);
15294     }
15295     if (boards[currentMove][y][x] != EmptySquare) {
15296         DisplayMoveError(_("That square is occupied"));
15297         return;
15298     }
15299
15300     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15301 }
15302
15303 void
15304 AcceptEvent ()
15305 {
15306     /* Accept a pending offer of any kind from opponent */
15307
15308     if (appData.icsActive) {
15309         SendToICS(ics_prefix);
15310         SendToICS("accept\n");
15311     } else if (cmailMsgLoaded) {
15312         if (currentMove == cmailOldMove &&
15313             commentList[cmailOldMove] != NULL &&
15314             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15315                    "Black offers a draw" : "White offers a draw")) {
15316             TruncateGame();
15317             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15318             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15319         } else {
15320             DisplayError(_("There is no pending offer on this move"), 0);
15321             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15322         }
15323     } else {
15324         /* Not used for offers from chess program */
15325     }
15326 }
15327
15328 void
15329 DeclineEvent ()
15330 {
15331     /* Decline a pending offer of any kind from opponent */
15332
15333     if (appData.icsActive) {
15334         SendToICS(ics_prefix);
15335         SendToICS("decline\n");
15336     } else if (cmailMsgLoaded) {
15337         if (currentMove == cmailOldMove &&
15338             commentList[cmailOldMove] != NULL &&
15339             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15340                    "Black offers a draw" : "White offers a draw")) {
15341 #ifdef NOTDEF
15342             AppendComment(cmailOldMove, "Draw declined", TRUE);
15343             DisplayComment(cmailOldMove - 1, "Draw declined");
15344 #endif /*NOTDEF*/
15345         } else {
15346             DisplayError(_("There is no pending offer on this move"), 0);
15347         }
15348     } else {
15349         /* Not used for offers from chess program */
15350     }
15351 }
15352
15353 void
15354 RematchEvent ()
15355 {
15356     /* Issue ICS rematch command */
15357     if (appData.icsActive) {
15358         SendToICS(ics_prefix);
15359         SendToICS("rematch\n");
15360     }
15361 }
15362
15363 void
15364 CallFlagEvent ()
15365 {
15366     /* Call your opponent's flag (claim a win on time) */
15367     if (appData.icsActive) {
15368         SendToICS(ics_prefix);
15369         SendToICS("flag\n");
15370     } else {
15371         switch (gameMode) {
15372           default:
15373             return;
15374           case MachinePlaysWhite:
15375             if (whiteFlag) {
15376                 if (blackFlag)
15377                   GameEnds(GameIsDrawn, "Both players ran out of time",
15378                            GE_PLAYER);
15379                 else
15380                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15381             } else {
15382                 DisplayError(_("Your opponent is not out of time"), 0);
15383             }
15384             break;
15385           case MachinePlaysBlack:
15386             if (blackFlag) {
15387                 if (whiteFlag)
15388                   GameEnds(GameIsDrawn, "Both players ran out of time",
15389                            GE_PLAYER);
15390                 else
15391                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15392             } else {
15393                 DisplayError(_("Your opponent is not out of time"), 0);
15394             }
15395             break;
15396         }
15397     }
15398 }
15399
15400 void
15401 ClockClick (int which)
15402 {       // [HGM] code moved to back-end from winboard.c
15403         if(which) { // black clock
15404           if (gameMode == EditPosition || gameMode == IcsExamining) {
15405             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15406             SetBlackToPlayEvent();
15407           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15408                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15409           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15410           } else if (shiftKey) {
15411             AdjustClock(which, -1);
15412           } else if (gameMode == IcsPlayingWhite ||
15413                      gameMode == MachinePlaysBlack) {
15414             CallFlagEvent();
15415           }
15416         } else { // white clock
15417           if (gameMode == EditPosition || gameMode == IcsExamining) {
15418             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15419             SetWhiteToPlayEvent();
15420           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15421                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15422           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15423           } else if (shiftKey) {
15424             AdjustClock(which, -1);
15425           } else if (gameMode == IcsPlayingBlack ||
15426                    gameMode == MachinePlaysWhite) {
15427             CallFlagEvent();
15428           }
15429         }
15430 }
15431
15432 void
15433 DrawEvent ()
15434 {
15435     /* Offer draw or accept pending draw offer from opponent */
15436
15437     if (appData.icsActive) {
15438         /* Note: tournament rules require draw offers to be
15439            made after you make your move but before you punch
15440            your clock.  Currently ICS doesn't let you do that;
15441            instead, you immediately punch your clock after making
15442            a move, but you can offer a draw at any time. */
15443
15444         SendToICS(ics_prefix);
15445         SendToICS("draw\n");
15446         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15447     } else if (cmailMsgLoaded) {
15448         if (currentMove == cmailOldMove &&
15449             commentList[cmailOldMove] != NULL &&
15450             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15451                    "Black offers a draw" : "White offers a draw")) {
15452             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15453             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15454         } else if (currentMove == cmailOldMove + 1) {
15455             char *offer = WhiteOnMove(cmailOldMove) ?
15456               "White offers a draw" : "Black offers a draw";
15457             AppendComment(currentMove, offer, TRUE);
15458             DisplayComment(currentMove - 1, offer);
15459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15460         } else {
15461             DisplayError(_("You must make your move before offering a draw"), 0);
15462             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15463         }
15464     } else if (first.offeredDraw) {
15465         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15466     } else {
15467         if (first.sendDrawOffers) {
15468             SendToProgram("draw\n", &first);
15469             userOfferedDraw = TRUE;
15470         }
15471     }
15472 }
15473
15474 void
15475 AdjournEvent ()
15476 {
15477     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15478
15479     if (appData.icsActive) {
15480         SendToICS(ics_prefix);
15481         SendToICS("adjourn\n");
15482     } else {
15483         /* Currently GNU Chess doesn't offer or accept Adjourns */
15484     }
15485 }
15486
15487
15488 void
15489 AbortEvent ()
15490 {
15491     /* Offer Abort or accept pending Abort offer from opponent */
15492
15493     if (appData.icsActive) {
15494         SendToICS(ics_prefix);
15495         SendToICS("abort\n");
15496     } else {
15497         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15498     }
15499 }
15500
15501 void
15502 ResignEvent ()
15503 {
15504     /* Resign.  You can do this even if it's not your turn. */
15505
15506     if (appData.icsActive) {
15507         SendToICS(ics_prefix);
15508         SendToICS("resign\n");
15509     } else {
15510         switch (gameMode) {
15511           case MachinePlaysWhite:
15512             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15513             break;
15514           case MachinePlaysBlack:
15515             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15516             break;
15517           case EditGame:
15518             if (cmailMsgLoaded) {
15519                 TruncateGame();
15520                 if (WhiteOnMove(cmailOldMove)) {
15521                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15522                 } else {
15523                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15524                 }
15525                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15526             }
15527             break;
15528           default:
15529             break;
15530         }
15531     }
15532 }
15533
15534
15535 void
15536 StopObservingEvent ()
15537 {
15538     /* Stop observing current games */
15539     SendToICS(ics_prefix);
15540     SendToICS("unobserve\n");
15541 }
15542
15543 void
15544 StopExaminingEvent ()
15545 {
15546     /* Stop observing current game */
15547     SendToICS(ics_prefix);
15548     SendToICS("unexamine\n");
15549 }
15550
15551 void
15552 ForwardInner (int target)
15553 {
15554     int limit; int oldSeekGraphUp = seekGraphUp;
15555
15556     if (appData.debugMode)
15557         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15558                 target, currentMove, forwardMostMove);
15559
15560     if (gameMode == EditPosition)
15561       return;
15562
15563     seekGraphUp = FALSE;
15564     MarkTargetSquares(1);
15565     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15566
15567     if (gameMode == PlayFromGameFile && !pausing)
15568       PauseEvent();
15569
15570     if (gameMode == IcsExamining && pausing)
15571       limit = pauseExamForwardMostMove;
15572     else
15573       limit = forwardMostMove;
15574
15575     if (target > limit) target = limit;
15576
15577     if (target > 0 && moveList[target - 1][0]) {
15578         int fromX, fromY, toX, toY;
15579         toX = moveList[target - 1][2] - AAA;
15580         toY = moveList[target - 1][3] - ONE;
15581         if (moveList[target - 1][1] == '@') {
15582             if (appData.highlightLastMove) {
15583                 SetHighlights(-1, -1, toX, toY);
15584             }
15585         } else {
15586             int viaX = moveList[target - 1][5] - AAA;
15587             int viaY = moveList[target - 1][6] - ONE;
15588             fromX = moveList[target - 1][0] - AAA;
15589             fromY = moveList[target - 1][1] - ONE;
15590             if (target == currentMove + 1) {
15591                 if(moveList[target - 1][4] == ';') { // multi-leg
15592                     ChessSquare piece = boards[currentMove][viaY][viaX];
15593                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15594                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15595                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15596                     boards[currentMove][viaY][viaX] = piece;
15597                 } else
15598                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15599             }
15600             if (appData.highlightLastMove) {
15601                 SetHighlights(fromX, fromY, toX, toY);
15602             }
15603         }
15604     }
15605     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15606         gameMode == Training || gameMode == PlayFromGameFile ||
15607         gameMode == AnalyzeFile) {
15608         while (currentMove < target) {
15609             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15610             SendMoveToProgram(currentMove++, &first);
15611         }
15612     } else {
15613         currentMove = target;
15614     }
15615
15616     if (gameMode == EditGame || gameMode == EndOfGame) {
15617         whiteTimeRemaining = timeRemaining[0][currentMove];
15618         blackTimeRemaining = timeRemaining[1][currentMove];
15619     }
15620     DisplayBothClocks();
15621     DisplayMove(currentMove - 1);
15622     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15623     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15624     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15625         DisplayComment(currentMove - 1, commentList[currentMove]);
15626     }
15627     ClearMap(); // [HGM] exclude: invalidate map
15628 }
15629
15630
15631 void
15632 ForwardEvent ()
15633 {
15634     if (gameMode == IcsExamining && !pausing) {
15635         SendToICS(ics_prefix);
15636         SendToICS("forward\n");
15637     } else {
15638         ForwardInner(currentMove + 1);
15639     }
15640 }
15641
15642 void
15643 ToEndEvent ()
15644 {
15645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15646         /* to optimze, we temporarily turn off analysis mode while we feed
15647          * the remaining moves to the engine. Otherwise we get analysis output
15648          * after each move.
15649          */
15650         if (first.analysisSupport) {
15651           SendToProgram("exit\nforce\n", &first);
15652           first.analyzing = FALSE;
15653         }
15654     }
15655
15656     if (gameMode == IcsExamining && !pausing) {
15657         SendToICS(ics_prefix);
15658         SendToICS("forward 999999\n");
15659     } else {
15660         ForwardInner(forwardMostMove);
15661     }
15662
15663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15664         /* we have fed all the moves, so reactivate analysis mode */
15665         SendToProgram("analyze\n", &first);
15666         first.analyzing = TRUE;
15667         /*first.maybeThinking = TRUE;*/
15668         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15669     }
15670 }
15671
15672 void
15673 BackwardInner (int target)
15674 {
15675     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15676
15677     if (appData.debugMode)
15678         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15679                 target, currentMove, forwardMostMove);
15680
15681     if (gameMode == EditPosition) return;
15682     seekGraphUp = FALSE;
15683     MarkTargetSquares(1);
15684     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15685     if (currentMove <= backwardMostMove) {
15686         ClearHighlights();
15687         DrawPosition(full_redraw, boards[currentMove]);
15688         return;
15689     }
15690     if (gameMode == PlayFromGameFile && !pausing)
15691       PauseEvent();
15692
15693     if (moveList[target][0]) {
15694         int fromX, fromY, toX, toY;
15695         toX = moveList[target][2] - AAA;
15696         toY = moveList[target][3] - ONE;
15697         if (moveList[target][1] == '@') {
15698             if (appData.highlightLastMove) {
15699                 SetHighlights(-1, -1, toX, toY);
15700             }
15701         } else {
15702             fromX = moveList[target][0] - AAA;
15703             fromY = moveList[target][1] - ONE;
15704             if (target == currentMove - 1) {
15705                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15706             }
15707             if (appData.highlightLastMove) {
15708                 SetHighlights(fromX, fromY, toX, toY);
15709             }
15710         }
15711     }
15712     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15713         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15714         while (currentMove > target) {
15715             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15716                 // null move cannot be undone. Reload program with move history before it.
15717                 int i;
15718                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15719                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15720                 }
15721                 SendBoard(&first, i);
15722               if(second.analyzing) SendBoard(&second, i);
15723                 for(currentMove=i; currentMove<target; currentMove++) {
15724                     SendMoveToProgram(currentMove, &first);
15725                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15726                 }
15727                 break;
15728             }
15729             SendToBoth("undo\n");
15730             currentMove--;
15731         }
15732     } else {
15733         currentMove = target;
15734     }
15735
15736     if (gameMode == EditGame || gameMode == EndOfGame) {
15737         whiteTimeRemaining = timeRemaining[0][currentMove];
15738         blackTimeRemaining = timeRemaining[1][currentMove];
15739     }
15740     DisplayBothClocks();
15741     DisplayMove(currentMove - 1);
15742     DrawPosition(full_redraw, boards[currentMove]);
15743     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15744     // [HGM] PV info: routine tests if comment empty
15745     DisplayComment(currentMove - 1, commentList[currentMove]);
15746     ClearMap(); // [HGM] exclude: invalidate map
15747 }
15748
15749 void
15750 BackwardEvent ()
15751 {
15752     if (gameMode == IcsExamining && !pausing) {
15753         SendToICS(ics_prefix);
15754         SendToICS("backward\n");
15755     } else {
15756         BackwardInner(currentMove - 1);
15757     }
15758 }
15759
15760 void
15761 ToStartEvent ()
15762 {
15763     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15764         /* to optimize, we temporarily turn off analysis mode while we undo
15765          * all the moves. Otherwise we get analysis output after each undo.
15766          */
15767         if (first.analysisSupport) {
15768           SendToProgram("exit\nforce\n", &first);
15769           first.analyzing = FALSE;
15770         }
15771     }
15772
15773     if (gameMode == IcsExamining && !pausing) {
15774         SendToICS(ics_prefix);
15775         SendToICS("backward 999999\n");
15776     } else {
15777         BackwardInner(backwardMostMove);
15778     }
15779
15780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15781         /* we have fed all the moves, so reactivate analysis mode */
15782         SendToProgram("analyze\n", &first);
15783         first.analyzing = TRUE;
15784         /*first.maybeThinking = TRUE;*/
15785         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15786     }
15787 }
15788
15789 void
15790 ToNrEvent (int to)
15791 {
15792   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15793   if (to >= forwardMostMove) to = forwardMostMove;
15794   if (to <= backwardMostMove) to = backwardMostMove;
15795   if (to < currentMove) {
15796     BackwardInner(to);
15797   } else {
15798     ForwardInner(to);
15799   }
15800 }
15801
15802 void
15803 RevertEvent (Boolean annotate)
15804 {
15805     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15806         return;
15807     }
15808     if (gameMode != IcsExamining) {
15809         DisplayError(_("You are not examining a game"), 0);
15810         return;
15811     }
15812     if (pausing) {
15813         DisplayError(_("You can't revert while pausing"), 0);
15814         return;
15815     }
15816     SendToICS(ics_prefix);
15817     SendToICS("revert\n");
15818 }
15819
15820 void
15821 RetractMoveEvent ()
15822 {
15823     switch (gameMode) {
15824       case MachinePlaysWhite:
15825       case MachinePlaysBlack:
15826         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15827             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15828             return;
15829         }
15830         if (forwardMostMove < 2) return;
15831         currentMove = forwardMostMove = forwardMostMove - 2;
15832         whiteTimeRemaining = timeRemaining[0][currentMove];
15833         blackTimeRemaining = timeRemaining[1][currentMove];
15834         DisplayBothClocks();
15835         DisplayMove(currentMove - 1);
15836         ClearHighlights();/*!! could figure this out*/
15837         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15838         SendToProgram("remove\n", &first);
15839         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15840         break;
15841
15842       case BeginningOfGame:
15843       default:
15844         break;
15845
15846       case IcsPlayingWhite:
15847       case IcsPlayingBlack:
15848         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15849             SendToICS(ics_prefix);
15850             SendToICS("takeback 2\n");
15851         } else {
15852             SendToICS(ics_prefix);
15853             SendToICS("takeback 1\n");
15854         }
15855         break;
15856     }
15857 }
15858
15859 void
15860 MoveNowEvent ()
15861 {
15862     ChessProgramState *cps;
15863
15864     switch (gameMode) {
15865       case MachinePlaysWhite:
15866         if (!WhiteOnMove(forwardMostMove)) {
15867             DisplayError(_("It is your turn"), 0);
15868             return;
15869         }
15870         cps = &first;
15871         break;
15872       case MachinePlaysBlack:
15873         if (WhiteOnMove(forwardMostMove)) {
15874             DisplayError(_("It is your turn"), 0);
15875             return;
15876         }
15877         cps = &first;
15878         break;
15879       case TwoMachinesPlay:
15880         if (WhiteOnMove(forwardMostMove) ==
15881             (first.twoMachinesColor[0] == 'w')) {
15882             cps = &first;
15883         } else {
15884             cps = &second;
15885         }
15886         break;
15887       case BeginningOfGame:
15888       default:
15889         return;
15890     }
15891     SendToProgram("?\n", cps);
15892 }
15893
15894 void
15895 TruncateGameEvent ()
15896 {
15897     EditGameEvent();
15898     if (gameMode != EditGame) return;
15899     TruncateGame();
15900 }
15901
15902 void
15903 TruncateGame ()
15904 {
15905     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15906     if (forwardMostMove > currentMove) {
15907         if (gameInfo.resultDetails != NULL) {
15908             free(gameInfo.resultDetails);
15909             gameInfo.resultDetails = NULL;
15910             gameInfo.result = GameUnfinished;
15911         }
15912         forwardMostMove = currentMove;
15913         HistorySet(parseList, backwardMostMove, forwardMostMove,
15914                    currentMove-1);
15915     }
15916 }
15917
15918 void
15919 HintEvent ()
15920 {
15921     if (appData.noChessProgram) return;
15922     switch (gameMode) {
15923       case MachinePlaysWhite:
15924         if (WhiteOnMove(forwardMostMove)) {
15925             DisplayError(_("Wait until your turn."), 0);
15926             return;
15927         }
15928         break;
15929       case BeginningOfGame:
15930       case MachinePlaysBlack:
15931         if (!WhiteOnMove(forwardMostMove)) {
15932             DisplayError(_("Wait until your turn."), 0);
15933             return;
15934         }
15935         break;
15936       default:
15937         DisplayError(_("No hint available"), 0);
15938         return;
15939     }
15940     SendToProgram("hint\n", &first);
15941     hintRequested = TRUE;
15942 }
15943
15944 int
15945 SaveSelected (FILE *g, int dummy, char *dummy2)
15946 {
15947     ListGame * lg = (ListGame *) gameList.head;
15948     int nItem, cnt=0;
15949     FILE *f;
15950
15951     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15952         DisplayError(_("Game list not loaded or empty"), 0);
15953         return 0;
15954     }
15955
15956     creatingBook = TRUE; // suppresses stuff during load game
15957
15958     /* Get list size */
15959     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15960         if(lg->position >= 0) { // selected?
15961             LoadGame(f, nItem, "", TRUE);
15962             SaveGamePGN2(g); // leaves g open
15963             cnt++; DoEvents();
15964         }
15965         lg = (ListGame *) lg->node.succ;
15966     }
15967
15968     fclose(g);
15969     creatingBook = FALSE;
15970
15971     return cnt;
15972 }
15973
15974 void
15975 CreateBookEvent ()
15976 {
15977     ListGame * lg = (ListGame *) gameList.head;
15978     FILE *f, *g;
15979     int nItem;
15980     static int secondTime = FALSE;
15981
15982     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15983         DisplayError(_("Game list not loaded or empty"), 0);
15984         return;
15985     }
15986
15987     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15988         fclose(g);
15989         secondTime++;
15990         DisplayNote(_("Book file exists! Try again for overwrite."));
15991         return;
15992     }
15993
15994     creatingBook = TRUE;
15995     secondTime = FALSE;
15996
15997     /* Get list size */
15998     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15999         if(lg->position >= 0) {
16000             LoadGame(f, nItem, "", TRUE);
16001             AddGameToBook(TRUE);
16002             DoEvents();
16003         }
16004         lg = (ListGame *) lg->node.succ;
16005     }
16006
16007     creatingBook = FALSE;
16008     FlushBook();
16009 }
16010
16011 void
16012 BookEvent ()
16013 {
16014     if (appData.noChessProgram) return;
16015     switch (gameMode) {
16016       case MachinePlaysWhite:
16017         if (WhiteOnMove(forwardMostMove)) {
16018             DisplayError(_("Wait until your turn."), 0);
16019             return;
16020         }
16021         break;
16022       case BeginningOfGame:
16023       case MachinePlaysBlack:
16024         if (!WhiteOnMove(forwardMostMove)) {
16025             DisplayError(_("Wait until your turn."), 0);
16026             return;
16027         }
16028         break;
16029       case EditPosition:
16030         EditPositionDone(TRUE);
16031         break;
16032       case TwoMachinesPlay:
16033         return;
16034       default:
16035         break;
16036     }
16037     SendToProgram("bk\n", &first);
16038     bookOutput[0] = NULLCHAR;
16039     bookRequested = TRUE;
16040 }
16041
16042 void
16043 AboutGameEvent ()
16044 {
16045     char *tags = PGNTags(&gameInfo);
16046     TagsPopUp(tags, CmailMsg());
16047     free(tags);
16048 }
16049
16050 /* end button procedures */
16051
16052 void
16053 PrintPosition (FILE *fp, int move)
16054 {
16055     int i, j;
16056
16057     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16058         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16059             char c = PieceToChar(boards[move][i][j]);
16060             fputc(c == 'x' ? '.' : c, fp);
16061             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16062         }
16063     }
16064     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16065       fprintf(fp, "white to play\n");
16066     else
16067       fprintf(fp, "black to play\n");
16068 }
16069
16070 void
16071 PrintOpponents (FILE *fp)
16072 {
16073     if (gameInfo.white != NULL) {
16074         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16075     } else {
16076         fprintf(fp, "\n");
16077     }
16078 }
16079
16080 /* Find last component of program's own name, using some heuristics */
16081 void
16082 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16083 {
16084     char *p, *q, c;
16085     int local = (strcmp(host, "localhost") == 0);
16086     while (!local && (p = strchr(prog, ';')) != NULL) {
16087         p++;
16088         while (*p == ' ') p++;
16089         prog = p;
16090     }
16091     if (*prog == '"' || *prog == '\'') {
16092         q = strchr(prog + 1, *prog);
16093     } else {
16094         q = strchr(prog, ' ');
16095     }
16096     if (q == NULL) q = prog + strlen(prog);
16097     p = q;
16098     while (p >= prog && *p != '/' && *p != '\\') p--;
16099     p++;
16100     if(p == prog && *p == '"') p++;
16101     c = *q; *q = 0;
16102     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16103     memcpy(buf, p, q - p);
16104     buf[q - p] = NULLCHAR;
16105     if (!local) {
16106         strcat(buf, "@");
16107         strcat(buf, host);
16108     }
16109 }
16110
16111 char *
16112 TimeControlTagValue ()
16113 {
16114     char buf[MSG_SIZ];
16115     if (!appData.clockMode) {
16116       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16117     } else if (movesPerSession > 0) {
16118       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16119     } else if (timeIncrement == 0) {
16120       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16121     } else {
16122       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16123     }
16124     return StrSave(buf);
16125 }
16126
16127 void
16128 SetGameInfo ()
16129 {
16130     /* This routine is used only for certain modes */
16131     VariantClass v = gameInfo.variant;
16132     ChessMove r = GameUnfinished;
16133     char *p = NULL;
16134
16135     if(keepInfo) return;
16136
16137     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16138         r = gameInfo.result;
16139         p = gameInfo.resultDetails;
16140         gameInfo.resultDetails = NULL;
16141     }
16142     ClearGameInfo(&gameInfo);
16143     gameInfo.variant = v;
16144
16145     switch (gameMode) {
16146       case MachinePlaysWhite:
16147         gameInfo.event = StrSave( appData.pgnEventHeader );
16148         gameInfo.site = StrSave(HostName());
16149         gameInfo.date = PGNDate();
16150         gameInfo.round = StrSave("-");
16151         gameInfo.white = StrSave(first.tidy);
16152         gameInfo.black = StrSave(UserName());
16153         gameInfo.timeControl = TimeControlTagValue();
16154         break;
16155
16156       case MachinePlaysBlack:
16157         gameInfo.event = StrSave( appData.pgnEventHeader );
16158         gameInfo.site = StrSave(HostName());
16159         gameInfo.date = PGNDate();
16160         gameInfo.round = StrSave("-");
16161         gameInfo.white = StrSave(UserName());
16162         gameInfo.black = StrSave(first.tidy);
16163         gameInfo.timeControl = TimeControlTagValue();
16164         break;
16165
16166       case TwoMachinesPlay:
16167         gameInfo.event = StrSave( appData.pgnEventHeader );
16168         gameInfo.site = StrSave(HostName());
16169         gameInfo.date = PGNDate();
16170         if (roundNr > 0) {
16171             char buf[MSG_SIZ];
16172             snprintf(buf, MSG_SIZ, "%d", roundNr);
16173             gameInfo.round = StrSave(buf);
16174         } else {
16175             gameInfo.round = StrSave("-");
16176         }
16177         if (first.twoMachinesColor[0] == 'w') {
16178             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16179             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16180         } else {
16181             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16182             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16183         }
16184         gameInfo.timeControl = TimeControlTagValue();
16185         break;
16186
16187       case EditGame:
16188         gameInfo.event = StrSave("Edited game");
16189         gameInfo.site = StrSave(HostName());
16190         gameInfo.date = PGNDate();
16191         gameInfo.round = StrSave("-");
16192         gameInfo.white = StrSave("-");
16193         gameInfo.black = StrSave("-");
16194         gameInfo.result = r;
16195         gameInfo.resultDetails = p;
16196         break;
16197
16198       case EditPosition:
16199         gameInfo.event = StrSave("Edited position");
16200         gameInfo.site = StrSave(HostName());
16201         gameInfo.date = PGNDate();
16202         gameInfo.round = StrSave("-");
16203         gameInfo.white = StrSave("-");
16204         gameInfo.black = StrSave("-");
16205         break;
16206
16207       case IcsPlayingWhite:
16208       case IcsPlayingBlack:
16209       case IcsObserving:
16210       case IcsExamining:
16211         break;
16212
16213       case PlayFromGameFile:
16214         gameInfo.event = StrSave("Game from non-PGN file");
16215         gameInfo.site = StrSave(HostName());
16216         gameInfo.date = PGNDate();
16217         gameInfo.round = StrSave("-");
16218         gameInfo.white = StrSave("?");
16219         gameInfo.black = StrSave("?");
16220         break;
16221
16222       default:
16223         break;
16224     }
16225 }
16226
16227 void
16228 ReplaceComment (int index, char *text)
16229 {
16230     int len;
16231     char *p;
16232     float score;
16233
16234     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16235        pvInfoList[index-1].depth == len &&
16236        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16237        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16238     while (*text == '\n') text++;
16239     len = strlen(text);
16240     while (len > 0 && text[len - 1] == '\n') len--;
16241
16242     if (commentList[index] != NULL)
16243       free(commentList[index]);
16244
16245     if (len == 0) {
16246         commentList[index] = NULL;
16247         return;
16248     }
16249   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16250       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16251       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16252     commentList[index] = (char *) malloc(len + 2);
16253     strncpy(commentList[index], text, len);
16254     commentList[index][len] = '\n';
16255     commentList[index][len + 1] = NULLCHAR;
16256   } else {
16257     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16258     char *p;
16259     commentList[index] = (char *) malloc(len + 7);
16260     safeStrCpy(commentList[index], "{\n", 3);
16261     safeStrCpy(commentList[index]+2, text, len+1);
16262     commentList[index][len+2] = NULLCHAR;
16263     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16264     strcat(commentList[index], "\n}\n");
16265   }
16266 }
16267
16268 void
16269 CrushCRs (char *text)
16270 {
16271   char *p = text;
16272   char *q = text;
16273   char ch;
16274
16275   do {
16276     ch = *p++;
16277     if (ch == '\r') continue;
16278     *q++ = ch;
16279   } while (ch != '\0');
16280 }
16281
16282 void
16283 AppendComment (int index, char *text, Boolean addBraces)
16284 /* addBraces  tells if we should add {} */
16285 {
16286     int oldlen, len;
16287     char *old;
16288
16289 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16290     if(addBraces == 3) addBraces = 0; else // force appending literally
16291     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16292
16293     CrushCRs(text);
16294     while (*text == '\n') text++;
16295     len = strlen(text);
16296     while (len > 0 && text[len - 1] == '\n') len--;
16297     text[len] = NULLCHAR;
16298
16299     if (len == 0) return;
16300
16301     if (commentList[index] != NULL) {
16302       Boolean addClosingBrace = addBraces;
16303         old = commentList[index];
16304         oldlen = strlen(old);
16305         while(commentList[index][oldlen-1] ==  '\n')
16306           commentList[index][--oldlen] = NULLCHAR;
16307         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16308         safeStrCpy(commentList[index], old, oldlen + len + 6);
16309         free(old);
16310         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16311         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16312           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16313           while (*text == '\n') { text++; len--; }
16314           commentList[index][--oldlen] = NULLCHAR;
16315       }
16316         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16317         else          strcat(commentList[index], "\n");
16318         strcat(commentList[index], text);
16319         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16320         else          strcat(commentList[index], "\n");
16321     } else {
16322         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16323         if(addBraces)
16324           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16325         else commentList[index][0] = NULLCHAR;
16326         strcat(commentList[index], text);
16327         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16328         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16329     }
16330 }
16331
16332 static char *
16333 FindStr (char * text, char * sub_text)
16334 {
16335     char * result = strstr( text, sub_text );
16336
16337     if( result != NULL ) {
16338         result += strlen( sub_text );
16339     }
16340
16341     return result;
16342 }
16343
16344 /* [AS] Try to extract PV info from PGN comment */
16345 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16346 char *
16347 GetInfoFromComment (int index, char * text)
16348 {
16349     char * sep = text, *p;
16350
16351     if( text != NULL && index > 0 ) {
16352         int score = 0;
16353         int depth = 0;
16354         int time = -1, sec = 0, deci;
16355         char * s_eval = FindStr( text, "[%eval " );
16356         char * s_emt = FindStr( text, "[%emt " );
16357 #if 0
16358         if( s_eval != NULL || s_emt != NULL ) {
16359 #else
16360         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16361 #endif
16362             /* New style */
16363             char delim;
16364
16365             if( s_eval != NULL ) {
16366                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16367                     return text;
16368                 }
16369
16370                 if( delim != ']' ) {
16371                     return text;
16372                 }
16373             }
16374
16375             if( s_emt != NULL ) {
16376             }
16377                 return text;
16378         }
16379         else {
16380             /* We expect something like: [+|-]nnn.nn/dd */
16381             int score_lo = 0;
16382
16383             if(*text != '{') return text; // [HGM] braces: must be normal comment
16384
16385             sep = strchr( text, '/' );
16386             if( sep == NULL || sep < (text+4) ) {
16387                 return text;
16388             }
16389
16390             p = text;
16391             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16392             if(p[1] == '(') { // comment starts with PV
16393                p = strchr(p, ')'); // locate end of PV
16394                if(p == NULL || sep < p+5) return text;
16395                // at this point we have something like "{(.*) +0.23/6 ..."
16396                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16397                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16398                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16399             }
16400             time = -1; sec = -1; deci = -1;
16401             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16402                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16403                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16404                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16405                 return text;
16406             }
16407
16408             if( score_lo < 0 || score_lo >= 100 ) {
16409                 return text;
16410             }
16411
16412             if(sec >= 0) time = 600*time + 10*sec; else
16413             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16414
16415             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16416
16417             /* [HGM] PV time: now locate end of PV info */
16418             while( *++sep >= '0' && *sep <= '9'); // strip depth
16419             if(time >= 0)
16420             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16421             if(sec >= 0)
16422             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16423             if(deci >= 0)
16424             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16425             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16426         }
16427
16428         if( depth <= 0 ) {
16429             return text;
16430         }
16431
16432         if( time < 0 ) {
16433             time = -1;
16434         }
16435
16436         pvInfoList[index-1].depth = depth;
16437         pvInfoList[index-1].score = score;
16438         pvInfoList[index-1].time  = 10*time; // centi-sec
16439         if(*sep == '}') *sep = 0; else *--sep = '{';
16440         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16441     }
16442     return sep;
16443 }
16444
16445 void
16446 SendToProgram (char *message, ChessProgramState *cps)
16447 {
16448     int count, outCount, error;
16449     char buf[MSG_SIZ];
16450
16451     if (cps->pr == NoProc) return;
16452     Attention(cps);
16453
16454     if (appData.debugMode) {
16455         TimeMark now;
16456         GetTimeMark(&now);
16457         fprintf(debugFP, "%ld >%-6s: %s",
16458                 SubtractTimeMarks(&now, &programStartTime),
16459                 cps->which, message);
16460         if(serverFP)
16461             fprintf(serverFP, "%ld >%-6s: %s",
16462                 SubtractTimeMarks(&now, &programStartTime),
16463                 cps->which, message), fflush(serverFP);
16464     }
16465
16466     count = strlen(message);
16467     outCount = OutputToProcess(cps->pr, message, count, &error);
16468     if (outCount < count && !exiting
16469                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16470       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16471       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16472         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16473             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16474                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16475                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16476                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16477             } else {
16478                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16479                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16480                 gameInfo.result = res;
16481             }
16482             gameInfo.resultDetails = StrSave(buf);
16483         }
16484         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16485         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16486     }
16487 }
16488
16489 void
16490 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16491 {
16492     char *end_str;
16493     char buf[MSG_SIZ];
16494     ChessProgramState *cps = (ChessProgramState *)closure;
16495
16496     if (isr != cps->isr) return; /* Killed intentionally */
16497     if (count <= 0) {
16498         if (count == 0) {
16499             RemoveInputSource(cps->isr);
16500             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16501                     _(cps->which), cps->program);
16502             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16503             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16504                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16505                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16506                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16507                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16508                 } else {
16509                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16510                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16511                     gameInfo.result = res;
16512                 }
16513                 gameInfo.resultDetails = StrSave(buf);
16514             }
16515             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16516             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16517         } else {
16518             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16519                     _(cps->which), cps->program);
16520             RemoveInputSource(cps->isr);
16521
16522             /* [AS] Program is misbehaving badly... kill it */
16523             if( count == -2 ) {
16524                 DestroyChildProcess( cps->pr, 9 );
16525                 cps->pr = NoProc;
16526             }
16527
16528             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16529         }
16530         return;
16531     }
16532
16533     if ((end_str = strchr(message, '\r')) != NULL)
16534       *end_str = NULLCHAR;
16535     if ((end_str = strchr(message, '\n')) != NULL)
16536       *end_str = NULLCHAR;
16537
16538     if (appData.debugMode) {
16539         TimeMark now; int print = 1;
16540         char *quote = ""; char c; int i;
16541
16542         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16543                 char start = message[0];
16544                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16545                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16546                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16547                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16548                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16549                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16550                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16551                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16552                    sscanf(message, "hint: %c", &c)!=1 &&
16553                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16554                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16555                     print = (appData.engineComments >= 2);
16556                 }
16557                 message[0] = start; // restore original message
16558         }
16559         if(print) {
16560                 GetTimeMark(&now);
16561                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16562                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16563                         quote,
16564                         message);
16565                 if(serverFP)
16566                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16567                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16568                         quote,
16569                         message), fflush(serverFP);
16570         }
16571     }
16572
16573     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16574     if (appData.icsEngineAnalyze) {
16575         if (strstr(message, "whisper") != NULL ||
16576              strstr(message, "kibitz") != NULL ||
16577             strstr(message, "tellics") != NULL) return;
16578     }
16579
16580     HandleMachineMove(message, cps);
16581 }
16582
16583
16584 void
16585 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16586 {
16587     char buf[MSG_SIZ];
16588     int seconds;
16589
16590     if( timeControl_2 > 0 ) {
16591         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16592             tc = timeControl_2;
16593         }
16594     }
16595     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16596     inc /= cps->timeOdds;
16597     st  /= cps->timeOdds;
16598
16599     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16600
16601     if (st > 0) {
16602       /* Set exact time per move, normally using st command */
16603       if (cps->stKludge) {
16604         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16605         seconds = st % 60;
16606         if (seconds == 0) {
16607           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16608         } else {
16609           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16610         }
16611       } else {
16612         snprintf(buf, MSG_SIZ, "st %d\n", st);
16613       }
16614     } else {
16615       /* Set conventional or incremental time control, using level command */
16616       if (seconds == 0) {
16617         /* Note old gnuchess bug -- minutes:seconds used to not work.
16618            Fixed in later versions, but still avoid :seconds
16619            when seconds is 0. */
16620         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16621       } else {
16622         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16623                  seconds, inc/1000.);
16624       }
16625     }
16626     SendToProgram(buf, cps);
16627
16628     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16629     /* Orthogonally, limit search to given depth */
16630     if (sd > 0) {
16631       if (cps->sdKludge) {
16632         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16633       } else {
16634         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16635       }
16636       SendToProgram(buf, cps);
16637     }
16638
16639     if(cps->nps >= 0) { /* [HGM] nps */
16640         if(cps->supportsNPS == FALSE)
16641           cps->nps = -1; // don't use if engine explicitly says not supported!
16642         else {
16643           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16644           SendToProgram(buf, cps);
16645         }
16646     }
16647 }
16648
16649 ChessProgramState *
16650 WhitePlayer ()
16651 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16652 {
16653     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16654        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16655         return &second;
16656     return &first;
16657 }
16658
16659 void
16660 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16661 {
16662     char message[MSG_SIZ];
16663     long time, otime;
16664
16665     /* Note: this routine must be called when the clocks are stopped
16666        or when they have *just* been set or switched; otherwise
16667        it will be off by the time since the current tick started.
16668     */
16669     if (machineWhite) {
16670         time = whiteTimeRemaining / 10;
16671         otime = blackTimeRemaining / 10;
16672     } else {
16673         time = blackTimeRemaining / 10;
16674         otime = whiteTimeRemaining / 10;
16675     }
16676     /* [HGM] translate opponent's time by time-odds factor */
16677     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16678
16679     if (time <= 0) time = 1;
16680     if (otime <= 0) otime = 1;
16681
16682     snprintf(message, MSG_SIZ, "time %ld\n", time);
16683     SendToProgram(message, cps);
16684
16685     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16686     SendToProgram(message, cps);
16687 }
16688
16689 char *
16690 EngineDefinedVariant (ChessProgramState *cps, int n)
16691 {   // return name of n-th unknown variant that engine supports
16692     static char buf[MSG_SIZ];
16693     char *p, *s = cps->variants;
16694     if(!s) return NULL;
16695     do { // parse string from variants feature
16696       VariantClass v;
16697         p = strchr(s, ',');
16698         if(p) *p = NULLCHAR;
16699       v = StringToVariant(s);
16700       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16701         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16702             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16703                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16704                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16705                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16706             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16707         }
16708         if(p) *p++ = ',';
16709         if(n < 0) return buf;
16710     } while(s = p);
16711     return NULL;
16712 }
16713
16714 int
16715 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16716 {
16717   char buf[MSG_SIZ];
16718   int len = strlen(name);
16719   int val;
16720
16721   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16722     (*p) += len + 1;
16723     sscanf(*p, "%d", &val);
16724     *loc = (val != 0);
16725     while (**p && **p != ' ')
16726       (*p)++;
16727     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16728     SendToProgram(buf, cps);
16729     return TRUE;
16730   }
16731   return FALSE;
16732 }
16733
16734 int
16735 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16736 {
16737   char buf[MSG_SIZ];
16738   int len = strlen(name);
16739   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16740     (*p) += len + 1;
16741     sscanf(*p, "%d", loc);
16742     while (**p && **p != ' ') (*p)++;
16743     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16744     SendToProgram(buf, cps);
16745     return TRUE;
16746   }
16747   return FALSE;
16748 }
16749
16750 int
16751 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16752 {
16753   char buf[MSG_SIZ];
16754   int len = strlen(name);
16755   if (strncmp((*p), name, len) == 0
16756       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16757     (*p) += len + 2;
16758     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16759     sscanf(*p, "%[^\"]", *loc);
16760     while (**p && **p != '\"') (*p)++;
16761     if (**p == '\"') (*p)++;
16762     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16763     SendToProgram(buf, cps);
16764     return TRUE;
16765   }
16766   return FALSE;
16767 }
16768
16769 int
16770 ParseOption (Option *opt, ChessProgramState *cps)
16771 // [HGM] options: process the string that defines an engine option, and determine
16772 // name, type, default value, and allowed value range
16773 {
16774         char *p, *q, buf[MSG_SIZ];
16775         int n, min = (-1)<<31, max = 1<<31, def;
16776
16777         if(p = strstr(opt->name, " -spin ")) {
16778             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16779             if(max < min) max = min; // enforce consistency
16780             if(def < min) def = min;
16781             if(def > max) def = max;
16782             opt->value = def;
16783             opt->min = min;
16784             opt->max = max;
16785             opt->type = Spin;
16786         } else if((p = strstr(opt->name, " -slider "))) {
16787             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16788             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16789             if(max < min) max = min; // enforce consistency
16790             if(def < min) def = min;
16791             if(def > max) def = max;
16792             opt->value = def;
16793             opt->min = min;
16794             opt->max = max;
16795             opt->type = Spin; // Slider;
16796         } else if((p = strstr(opt->name, " -string "))) {
16797             opt->textValue = p+9;
16798             opt->type = TextBox;
16799         } else if((p = strstr(opt->name, " -file "))) {
16800             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16801             opt->textValue = p+7;
16802             opt->type = FileName; // FileName;
16803         } else if((p = strstr(opt->name, " -path "))) {
16804             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16805             opt->textValue = p+7;
16806             opt->type = PathName; // PathName;
16807         } else if(p = strstr(opt->name, " -check ")) {
16808             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16809             opt->value = (def != 0);
16810             opt->type = CheckBox;
16811         } else if(p = strstr(opt->name, " -combo ")) {
16812             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16813             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16814             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16815             opt->value = n = 0;
16816             while(q = StrStr(q, " /// ")) {
16817                 n++; *q = 0;    // count choices, and null-terminate each of them
16818                 q += 5;
16819                 if(*q == '*') { // remember default, which is marked with * prefix
16820                     q++;
16821                     opt->value = n;
16822                 }
16823                 cps->comboList[cps->comboCnt++] = q;
16824             }
16825             cps->comboList[cps->comboCnt++] = NULL;
16826             opt->max = n + 1;
16827             opt->type = ComboBox;
16828         } else if(p = strstr(opt->name, " -button")) {
16829             opt->type = Button;
16830         } else if(p = strstr(opt->name, " -save")) {
16831             opt->type = SaveButton;
16832         } else return FALSE;
16833         *p = 0; // terminate option name
16834         // now look if the command-line options define a setting for this engine option.
16835         if(cps->optionSettings && cps->optionSettings[0])
16836             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16837         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16838           snprintf(buf, MSG_SIZ, "option %s", p);
16839                 if(p = strstr(buf, ",")) *p = 0;
16840                 if(q = strchr(buf, '=')) switch(opt->type) {
16841                     case ComboBox:
16842                         for(n=0; n<opt->max; n++)
16843                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16844                         break;
16845                     case TextBox:
16846                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16847                         break;
16848                     case Spin:
16849                     case CheckBox:
16850                         opt->value = atoi(q+1);
16851                     default:
16852                         break;
16853                 }
16854                 strcat(buf, "\n");
16855                 SendToProgram(buf, cps);
16856         }
16857         return TRUE;
16858 }
16859
16860 void
16861 FeatureDone (ChessProgramState *cps, int val)
16862 {
16863   DelayedEventCallback cb = GetDelayedEvent();
16864   if ((cb == InitBackEnd3 && cps == &first) ||
16865       (cb == SettingsMenuIfReady && cps == &second) ||
16866       (cb == LoadEngine) ||
16867       (cb == TwoMachinesEventIfReady)) {
16868     CancelDelayedEvent();
16869     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16870   }
16871   cps->initDone = val;
16872   if(val) cps->reload = FALSE;
16873 }
16874
16875 /* Parse feature command from engine */
16876 void
16877 ParseFeatures (char *args, ChessProgramState *cps)
16878 {
16879   char *p = args;
16880   char *q = NULL;
16881   int val;
16882   char buf[MSG_SIZ];
16883
16884   for (;;) {
16885     while (*p == ' ') p++;
16886     if (*p == NULLCHAR) return;
16887
16888     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16889     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16890     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16891     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16892     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16893     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16894     if (BoolFeature(&p, "reuse", &val, cps)) {
16895       /* Engine can disable reuse, but can't enable it if user said no */
16896       if (!val) cps->reuse = FALSE;
16897       continue;
16898     }
16899     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16900     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16901       if (gameMode == TwoMachinesPlay) {
16902         DisplayTwoMachinesTitle();
16903       } else {
16904         DisplayTitle("");
16905       }
16906       continue;
16907     }
16908     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16909     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16910     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16911     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16912     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16913     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16914     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16915     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16916     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16917     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16918     if (IntFeature(&p, "done", &val, cps)) {
16919       FeatureDone(cps, val);
16920       continue;
16921     }
16922     /* Added by Tord: */
16923     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16924     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16925     /* End of additions by Tord */
16926
16927     /* [HGM] added features: */
16928     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16929     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16930     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16931     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16932     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16933     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16934     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16935     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16936         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16937         FREE(cps->option[cps->nrOptions].name);
16938         cps->option[cps->nrOptions].name = q; q = NULL;
16939         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16940           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16941             SendToProgram(buf, cps);
16942             continue;
16943         }
16944         if(cps->nrOptions >= MAX_OPTIONS) {
16945             cps->nrOptions--;
16946             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16947             DisplayError(buf, 0);
16948         }
16949         continue;
16950     }
16951     /* End of additions by HGM */
16952
16953     /* unknown feature: complain and skip */
16954     q = p;
16955     while (*q && *q != '=') q++;
16956     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16957     SendToProgram(buf, cps);
16958     p = q;
16959     if (*p == '=') {
16960       p++;
16961       if (*p == '\"') {
16962         p++;
16963         while (*p && *p != '\"') p++;
16964         if (*p == '\"') p++;
16965       } else {
16966         while (*p && *p != ' ') p++;
16967       }
16968     }
16969   }
16970
16971 }
16972
16973 void
16974 PeriodicUpdatesEvent (int newState)
16975 {
16976     if (newState == appData.periodicUpdates)
16977       return;
16978
16979     appData.periodicUpdates=newState;
16980
16981     /* Display type changes, so update it now */
16982 //    DisplayAnalysis();
16983
16984     /* Get the ball rolling again... */
16985     if (newState) {
16986         AnalysisPeriodicEvent(1);
16987         StartAnalysisClock();
16988     }
16989 }
16990
16991 void
16992 PonderNextMoveEvent (int newState)
16993 {
16994     if (newState == appData.ponderNextMove) return;
16995     if (gameMode == EditPosition) EditPositionDone(TRUE);
16996     if (newState) {
16997         SendToProgram("hard\n", &first);
16998         if (gameMode == TwoMachinesPlay) {
16999             SendToProgram("hard\n", &second);
17000         }
17001     } else {
17002         SendToProgram("easy\n", &first);
17003         thinkOutput[0] = NULLCHAR;
17004         if (gameMode == TwoMachinesPlay) {
17005             SendToProgram("easy\n", &second);
17006         }
17007     }
17008     appData.ponderNextMove = newState;
17009 }
17010
17011 void
17012 NewSettingEvent (int option, int *feature, char *command, int value)
17013 {
17014     char buf[MSG_SIZ];
17015
17016     if (gameMode == EditPosition) EditPositionDone(TRUE);
17017     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17018     if(feature == NULL || *feature) SendToProgram(buf, &first);
17019     if (gameMode == TwoMachinesPlay) {
17020         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17021     }
17022 }
17023
17024 void
17025 ShowThinkingEvent ()
17026 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17027 {
17028     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17029     int newState = appData.showThinking
17030         // [HGM] thinking: other features now need thinking output as well
17031         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17032
17033     if (oldState == newState) return;
17034     oldState = newState;
17035     if (gameMode == EditPosition) EditPositionDone(TRUE);
17036     if (oldState) {
17037         SendToProgram("post\n", &first);
17038         if (gameMode == TwoMachinesPlay) {
17039             SendToProgram("post\n", &second);
17040         }
17041     } else {
17042         SendToProgram("nopost\n", &first);
17043         thinkOutput[0] = NULLCHAR;
17044         if (gameMode == TwoMachinesPlay) {
17045             SendToProgram("nopost\n", &second);
17046         }
17047     }
17048 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17049 }
17050
17051 void
17052 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17053 {
17054   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17055   if (pr == NoProc) return;
17056   AskQuestion(title, question, replyPrefix, pr);
17057 }
17058
17059 void
17060 TypeInEvent (char firstChar)
17061 {
17062     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17063         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17064         gameMode == AnalyzeMode || gameMode == EditGame ||
17065         gameMode == EditPosition || gameMode == IcsExamining ||
17066         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17067         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17068                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17069                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17070         gameMode == Training) PopUpMoveDialog(firstChar);
17071 }
17072
17073 void
17074 TypeInDoneEvent (char *move)
17075 {
17076         Board board;
17077         int n, fromX, fromY, toX, toY;
17078         char promoChar;
17079         ChessMove moveType;
17080
17081         // [HGM] FENedit
17082         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17083                 EditPositionPasteFEN(move);
17084                 return;
17085         }
17086         // [HGM] movenum: allow move number to be typed in any mode
17087         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17088           ToNrEvent(2*n-1);
17089           return;
17090         }
17091         // undocumented kludge: allow command-line option to be typed in!
17092         // (potentially fatal, and does not implement the effect of the option.)
17093         // should only be used for options that are values on which future decisions will be made,
17094         // and definitely not on options that would be used during initialization.
17095         if(strstr(move, "!!! -") == move) {
17096             ParseArgsFromString(move+4);
17097             return;
17098         }
17099
17100       if (gameMode != EditGame && currentMove != forwardMostMove &&
17101         gameMode != Training) {
17102         DisplayMoveError(_("Displayed move is not current"));
17103       } else {
17104         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17105           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17106         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17107         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17108           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17109           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17110         } else {
17111           DisplayMoveError(_("Could not parse move"));
17112         }
17113       }
17114 }
17115
17116 void
17117 DisplayMove (int moveNumber)
17118 {
17119     char message[MSG_SIZ];
17120     char res[MSG_SIZ];
17121     char cpThinkOutput[MSG_SIZ];
17122
17123     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17124
17125     if (moveNumber == forwardMostMove - 1 ||
17126         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17127
17128         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17129
17130         if (strchr(cpThinkOutput, '\n')) {
17131             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17132         }
17133     } else {
17134         *cpThinkOutput = NULLCHAR;
17135     }
17136
17137     /* [AS] Hide thinking from human user */
17138     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17139         *cpThinkOutput = NULLCHAR;
17140         if( thinkOutput[0] != NULLCHAR ) {
17141             int i;
17142
17143             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17144                 cpThinkOutput[i] = '.';
17145             }
17146             cpThinkOutput[i] = NULLCHAR;
17147             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17148         }
17149     }
17150
17151     if (moveNumber == forwardMostMove - 1 &&
17152         gameInfo.resultDetails != NULL) {
17153         if (gameInfo.resultDetails[0] == NULLCHAR) {
17154           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17155         } else {
17156           snprintf(res, MSG_SIZ, " {%s} %s",
17157                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17158         }
17159     } else {
17160         res[0] = NULLCHAR;
17161     }
17162
17163     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17164         DisplayMessage(res, cpThinkOutput);
17165     } else {
17166       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17167                 WhiteOnMove(moveNumber) ? " " : ".. ",
17168                 parseList[moveNumber], res);
17169         DisplayMessage(message, cpThinkOutput);
17170     }
17171 }
17172
17173 void
17174 DisplayComment (int moveNumber, char *text)
17175 {
17176     char title[MSG_SIZ];
17177
17178     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17179       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17180     } else {
17181       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17182               WhiteOnMove(moveNumber) ? " " : ".. ",
17183               parseList[moveNumber]);
17184     }
17185     if (text != NULL && (appData.autoDisplayComment || commentUp))
17186         CommentPopUp(title, text);
17187 }
17188
17189 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17190  * might be busy thinking or pondering.  It can be omitted if your
17191  * gnuchess is configured to stop thinking immediately on any user
17192  * input.  However, that gnuchess feature depends on the FIONREAD
17193  * ioctl, which does not work properly on some flavors of Unix.
17194  */
17195 void
17196 Attention (ChessProgramState *cps)
17197 {
17198 #if ATTENTION
17199     if (!cps->useSigint) return;
17200     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17201     switch (gameMode) {
17202       case MachinePlaysWhite:
17203       case MachinePlaysBlack:
17204       case TwoMachinesPlay:
17205       case IcsPlayingWhite:
17206       case IcsPlayingBlack:
17207       case AnalyzeMode:
17208       case AnalyzeFile:
17209         /* Skip if we know it isn't thinking */
17210         if (!cps->maybeThinking) return;
17211         if (appData.debugMode)
17212           fprintf(debugFP, "Interrupting %s\n", cps->which);
17213         InterruptChildProcess(cps->pr);
17214         cps->maybeThinking = FALSE;
17215         break;
17216       default:
17217         break;
17218     }
17219 #endif /*ATTENTION*/
17220 }
17221
17222 int
17223 CheckFlags ()
17224 {
17225     if (whiteTimeRemaining <= 0) {
17226         if (!whiteFlag) {
17227             whiteFlag = TRUE;
17228             if (appData.icsActive) {
17229                 if (appData.autoCallFlag &&
17230                     gameMode == IcsPlayingBlack && !blackFlag) {
17231                   SendToICS(ics_prefix);
17232                   SendToICS("flag\n");
17233                 }
17234             } else {
17235                 if (blackFlag) {
17236                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17237                 } else {
17238                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17239                     if (appData.autoCallFlag) {
17240                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17241                         return TRUE;
17242                     }
17243                 }
17244             }
17245         }
17246     }
17247     if (blackTimeRemaining <= 0) {
17248         if (!blackFlag) {
17249             blackFlag = TRUE;
17250             if (appData.icsActive) {
17251                 if (appData.autoCallFlag &&
17252                     gameMode == IcsPlayingWhite && !whiteFlag) {
17253                   SendToICS(ics_prefix);
17254                   SendToICS("flag\n");
17255                 }
17256             } else {
17257                 if (whiteFlag) {
17258                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17259                 } else {
17260                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17261                     if (appData.autoCallFlag) {
17262                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17263                         return TRUE;
17264                     }
17265                 }
17266             }
17267         }
17268     }
17269     return FALSE;
17270 }
17271
17272 void
17273 CheckTimeControl ()
17274 {
17275     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17276         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17277
17278     /*
17279      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17280      */
17281     if ( !WhiteOnMove(forwardMostMove) ) {
17282         /* White made time control */
17283         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17284         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17285         /* [HGM] time odds: correct new time quota for time odds! */
17286                                             / WhitePlayer()->timeOdds;
17287         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17288     } else {
17289         lastBlack -= blackTimeRemaining;
17290         /* Black made time control */
17291         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17292                                             / WhitePlayer()->other->timeOdds;
17293         lastWhite = whiteTimeRemaining;
17294     }
17295 }
17296
17297 void
17298 DisplayBothClocks ()
17299 {
17300     int wom = gameMode == EditPosition ?
17301       !blackPlaysFirst : WhiteOnMove(currentMove);
17302     DisplayWhiteClock(whiteTimeRemaining, wom);
17303     DisplayBlackClock(blackTimeRemaining, !wom);
17304 }
17305
17306
17307 /* Timekeeping seems to be a portability nightmare.  I think everyone
17308    has ftime(), but I'm really not sure, so I'm including some ifdefs
17309    to use other calls if you don't.  Clocks will be less accurate if
17310    you have neither ftime nor gettimeofday.
17311 */
17312
17313 /* VS 2008 requires the #include outside of the function */
17314 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17315 #include <sys/timeb.h>
17316 #endif
17317
17318 /* Get the current time as a TimeMark */
17319 void
17320 GetTimeMark (TimeMark *tm)
17321 {
17322 #if HAVE_GETTIMEOFDAY
17323
17324     struct timeval timeVal;
17325     struct timezone timeZone;
17326
17327     gettimeofday(&timeVal, &timeZone);
17328     tm->sec = (long) timeVal.tv_sec;
17329     tm->ms = (int) (timeVal.tv_usec / 1000L);
17330
17331 #else /*!HAVE_GETTIMEOFDAY*/
17332 #if HAVE_FTIME
17333
17334 // include <sys/timeb.h> / moved to just above start of function
17335     struct timeb timeB;
17336
17337     ftime(&timeB);
17338     tm->sec = (long) timeB.time;
17339     tm->ms = (int) timeB.millitm;
17340
17341 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17342     tm->sec = (long) time(NULL);
17343     tm->ms = 0;
17344 #endif
17345 #endif
17346 }
17347
17348 /* Return the difference in milliseconds between two
17349    time marks.  We assume the difference will fit in a long!
17350 */
17351 long
17352 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17353 {
17354     return 1000L*(tm2->sec - tm1->sec) +
17355            (long) (tm2->ms - tm1->ms);
17356 }
17357
17358
17359 /*
17360  * Code to manage the game clocks.
17361  *
17362  * In tournament play, black starts the clock and then white makes a move.
17363  * We give the human user a slight advantage if he is playing white---the
17364  * clocks don't run until he makes his first move, so it takes zero time.
17365  * Also, we don't account for network lag, so we could get out of sync
17366  * with GNU Chess's clock -- but then, referees are always right.
17367  */
17368
17369 static TimeMark tickStartTM;
17370 static long intendedTickLength;
17371
17372 long
17373 NextTickLength (long timeRemaining)
17374 {
17375     long nominalTickLength, nextTickLength;
17376
17377     if (timeRemaining > 0L && timeRemaining <= 10000L)
17378       nominalTickLength = 100L;
17379     else
17380       nominalTickLength = 1000L;
17381     nextTickLength = timeRemaining % nominalTickLength;
17382     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17383
17384     return nextTickLength;
17385 }
17386
17387 /* Adjust clock one minute up or down */
17388 void
17389 AdjustClock (Boolean which, int dir)
17390 {
17391     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17392     if(which) blackTimeRemaining += 60000*dir;
17393     else      whiteTimeRemaining += 60000*dir;
17394     DisplayBothClocks();
17395     adjustedClock = TRUE;
17396 }
17397
17398 /* Stop clocks and reset to a fresh time control */
17399 void
17400 ResetClocks ()
17401 {
17402     (void) StopClockTimer();
17403     if (appData.icsActive) {
17404         whiteTimeRemaining = blackTimeRemaining = 0;
17405     } else if (searchTime) {
17406         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17407         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17408     } else { /* [HGM] correct new time quote for time odds */
17409         whiteTC = blackTC = fullTimeControlString;
17410         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17411         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17412     }
17413     if (whiteFlag || blackFlag) {
17414         DisplayTitle("");
17415         whiteFlag = blackFlag = FALSE;
17416     }
17417     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17418     DisplayBothClocks();
17419     adjustedClock = FALSE;
17420 }
17421
17422 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17423
17424 /* Decrement running clock by amount of time that has passed */
17425 void
17426 DecrementClocks ()
17427 {
17428     long timeRemaining;
17429     long lastTickLength, fudge;
17430     TimeMark now;
17431
17432     if (!appData.clockMode) return;
17433     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17434
17435     GetTimeMark(&now);
17436
17437     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17438
17439     /* Fudge if we woke up a little too soon */
17440     fudge = intendedTickLength - lastTickLength;
17441     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17442
17443     if (WhiteOnMove(forwardMostMove)) {
17444         if(whiteNPS >= 0) lastTickLength = 0;
17445         timeRemaining = whiteTimeRemaining -= lastTickLength;
17446         if(timeRemaining < 0 && !appData.icsActive) {
17447             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17448             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17449                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17450                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17451             }
17452         }
17453         DisplayWhiteClock(whiteTimeRemaining - fudge,
17454                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17455     } else {
17456         if(blackNPS >= 0) lastTickLength = 0;
17457         timeRemaining = blackTimeRemaining -= lastTickLength;
17458         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17459             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17460             if(suddenDeath) {
17461                 blackStartMove = forwardMostMove;
17462                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17463             }
17464         }
17465         DisplayBlackClock(blackTimeRemaining - fudge,
17466                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17467     }
17468     if (CheckFlags()) return;
17469
17470     if(twoBoards) { // count down secondary board's clocks as well
17471         activePartnerTime -= lastTickLength;
17472         partnerUp = 1;
17473         if(activePartner == 'W')
17474             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17475         else
17476             DisplayBlackClock(activePartnerTime, TRUE);
17477         partnerUp = 0;
17478     }
17479
17480     tickStartTM = now;
17481     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17482     StartClockTimer(intendedTickLength);
17483
17484     /* if the time remaining has fallen below the alarm threshold, sound the
17485      * alarm. if the alarm has sounded and (due to a takeback or time control
17486      * with increment) the time remaining has increased to a level above the
17487      * threshold, reset the alarm so it can sound again.
17488      */
17489
17490     if (appData.icsActive && appData.icsAlarm) {
17491
17492         /* make sure we are dealing with the user's clock */
17493         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17494                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17495            )) return;
17496
17497         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17498             alarmSounded = FALSE;
17499         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17500             PlayAlarmSound();
17501             alarmSounded = TRUE;
17502         }
17503     }
17504 }
17505
17506
17507 /* A player has just moved, so stop the previously running
17508    clock and (if in clock mode) start the other one.
17509    We redisplay both clocks in case we're in ICS mode, because
17510    ICS gives us an update to both clocks after every move.
17511    Note that this routine is called *after* forwardMostMove
17512    is updated, so the last fractional tick must be subtracted
17513    from the color that is *not* on move now.
17514 */
17515 void
17516 SwitchClocks (int newMoveNr)
17517 {
17518     long lastTickLength;
17519     TimeMark now;
17520     int flagged = FALSE;
17521
17522     GetTimeMark(&now);
17523
17524     if (StopClockTimer() && appData.clockMode) {
17525         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17526         if (!WhiteOnMove(forwardMostMove)) {
17527             if(blackNPS >= 0) lastTickLength = 0;
17528             blackTimeRemaining -= lastTickLength;
17529            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17530 //         if(pvInfoList[forwardMostMove].time == -1)
17531                  pvInfoList[forwardMostMove].time =               // use GUI time
17532                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17533         } else {
17534            if(whiteNPS >= 0) lastTickLength = 0;
17535            whiteTimeRemaining -= lastTickLength;
17536            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17537 //         if(pvInfoList[forwardMostMove].time == -1)
17538                  pvInfoList[forwardMostMove].time =
17539                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17540         }
17541         flagged = CheckFlags();
17542     }
17543     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17544     CheckTimeControl();
17545
17546     if (flagged || !appData.clockMode) return;
17547
17548     switch (gameMode) {
17549       case MachinePlaysBlack:
17550       case MachinePlaysWhite:
17551       case BeginningOfGame:
17552         if (pausing) return;
17553         break;
17554
17555       case EditGame:
17556       case PlayFromGameFile:
17557       case IcsExamining:
17558         return;
17559
17560       default:
17561         break;
17562     }
17563
17564     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17565         if(WhiteOnMove(forwardMostMove))
17566              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17567         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17568     }
17569
17570     tickStartTM = now;
17571     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17572       whiteTimeRemaining : blackTimeRemaining);
17573     StartClockTimer(intendedTickLength);
17574 }
17575
17576
17577 /* Stop both clocks */
17578 void
17579 StopClocks ()
17580 {
17581     long lastTickLength;
17582     TimeMark now;
17583
17584     if (!StopClockTimer()) return;
17585     if (!appData.clockMode) return;
17586
17587     GetTimeMark(&now);
17588
17589     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17590     if (WhiteOnMove(forwardMostMove)) {
17591         if(whiteNPS >= 0) lastTickLength = 0;
17592         whiteTimeRemaining -= lastTickLength;
17593         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17594     } else {
17595         if(blackNPS >= 0) lastTickLength = 0;
17596         blackTimeRemaining -= lastTickLength;
17597         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17598     }
17599     CheckFlags();
17600 }
17601
17602 /* Start clock of player on move.  Time may have been reset, so
17603    if clock is already running, stop and restart it. */
17604 void
17605 StartClocks ()
17606 {
17607     (void) StopClockTimer(); /* in case it was running already */
17608     DisplayBothClocks();
17609     if (CheckFlags()) return;
17610
17611     if (!appData.clockMode) return;
17612     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17613
17614     GetTimeMark(&tickStartTM);
17615     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17616       whiteTimeRemaining : blackTimeRemaining);
17617
17618    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17619     whiteNPS = blackNPS = -1;
17620     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17621        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17622         whiteNPS = first.nps;
17623     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17624        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17625         blackNPS = first.nps;
17626     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17627         whiteNPS = second.nps;
17628     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17629         blackNPS = second.nps;
17630     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17631
17632     StartClockTimer(intendedTickLength);
17633 }
17634
17635 char *
17636 TimeString (long ms)
17637 {
17638     long second, minute, hour, day;
17639     char *sign = "";
17640     static char buf[32];
17641
17642     if (ms > 0 && ms <= 9900) {
17643       /* convert milliseconds to tenths, rounding up */
17644       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17645
17646       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17647       return buf;
17648     }
17649
17650     /* convert milliseconds to seconds, rounding up */
17651     /* use floating point to avoid strangeness of integer division
17652        with negative dividends on many machines */
17653     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17654
17655     if (second < 0) {
17656         sign = "-";
17657         second = -second;
17658     }
17659
17660     day = second / (60 * 60 * 24);
17661     second = second % (60 * 60 * 24);
17662     hour = second / (60 * 60);
17663     second = second % (60 * 60);
17664     minute = second / 60;
17665     second = second % 60;
17666
17667     if (day > 0)
17668       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17669               sign, day, hour, minute, second);
17670     else if (hour > 0)
17671       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17672     else
17673       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17674
17675     return buf;
17676 }
17677
17678
17679 /*
17680  * This is necessary because some C libraries aren't ANSI C compliant yet.
17681  */
17682 char *
17683 StrStr (char *string, char *match)
17684 {
17685     int i, length;
17686
17687     length = strlen(match);
17688
17689     for (i = strlen(string) - length; i >= 0; i--, string++)
17690       if (!strncmp(match, string, length))
17691         return string;
17692
17693     return NULL;
17694 }
17695
17696 char *
17697 StrCaseStr (char *string, char *match)
17698 {
17699     int i, j, length;
17700
17701     length = strlen(match);
17702
17703     for (i = strlen(string) - length; i >= 0; i--, string++) {
17704         for (j = 0; j < length; j++) {
17705             if (ToLower(match[j]) != ToLower(string[j]))
17706               break;
17707         }
17708         if (j == length) return string;
17709     }
17710
17711     return NULL;
17712 }
17713
17714 #ifndef _amigados
17715 int
17716 StrCaseCmp (char *s1, char *s2)
17717 {
17718     char c1, c2;
17719
17720     for (;;) {
17721         c1 = ToLower(*s1++);
17722         c2 = ToLower(*s2++);
17723         if (c1 > c2) return 1;
17724         if (c1 < c2) return -1;
17725         if (c1 == NULLCHAR) return 0;
17726     }
17727 }
17728
17729
17730 int
17731 ToLower (int c)
17732 {
17733     return isupper(c) ? tolower(c) : c;
17734 }
17735
17736
17737 int
17738 ToUpper (int c)
17739 {
17740     return islower(c) ? toupper(c) : c;
17741 }
17742 #endif /* !_amigados    */
17743
17744 char *
17745 StrSave (char *s)
17746 {
17747   char *ret;
17748
17749   if ((ret = (char *) malloc(strlen(s) + 1)))
17750     {
17751       safeStrCpy(ret, s, strlen(s)+1);
17752     }
17753   return ret;
17754 }
17755
17756 char *
17757 StrSavePtr (char *s, char **savePtr)
17758 {
17759     if (*savePtr) {
17760         free(*savePtr);
17761     }
17762     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17763       safeStrCpy(*savePtr, s, strlen(s)+1);
17764     }
17765     return(*savePtr);
17766 }
17767
17768 char *
17769 PGNDate ()
17770 {
17771     time_t clock;
17772     struct tm *tm;
17773     char buf[MSG_SIZ];
17774
17775     clock = time((time_t *)NULL);
17776     tm = localtime(&clock);
17777     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17778             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17779     return StrSave(buf);
17780 }
17781
17782
17783 char *
17784 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17785 {
17786     int i, j, fromX, fromY, toX, toY;
17787     int whiteToPlay;
17788     char buf[MSG_SIZ];
17789     char *p, *q;
17790     int emptycount;
17791     ChessSquare piece;
17792
17793     whiteToPlay = (gameMode == EditPosition) ?
17794       !blackPlaysFirst : (move % 2 == 0);
17795     p = buf;
17796
17797     /* Piece placement data */
17798     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17799         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17800         emptycount = 0;
17801         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17802             if (boards[move][i][j] == EmptySquare) {
17803                 emptycount++;
17804             } else { ChessSquare piece = boards[move][i][j];
17805                 if (emptycount > 0) {
17806                     if(emptycount<10) /* [HGM] can be >= 10 */
17807                         *p++ = '0' + emptycount;
17808                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17809                     emptycount = 0;
17810                 }
17811                 if(PieceToChar(piece) == '+') {
17812                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17813                     *p++ = '+';
17814                     piece = (ChessSquare)(CHUDEMOTED piece);
17815                 }
17816                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17817                 if(p[-1] == '~') {
17818                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17819                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17820                     *p++ = '~';
17821                 }
17822             }
17823         }
17824         if (emptycount > 0) {
17825             if(emptycount<10) /* [HGM] can be >= 10 */
17826                 *p++ = '0' + emptycount;
17827             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17828             emptycount = 0;
17829         }
17830         *p++ = '/';
17831     }
17832     *(p - 1) = ' ';
17833
17834     /* [HGM] print Crazyhouse or Shogi holdings */
17835     if( gameInfo.holdingsWidth ) {
17836         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17837         q = p;
17838         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17839             piece = boards[move][i][BOARD_WIDTH-1];
17840             if( piece != EmptySquare )
17841               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17842                   *p++ = PieceToChar(piece);
17843         }
17844         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17845             piece = boards[move][BOARD_HEIGHT-i-1][0];
17846             if( piece != EmptySquare )
17847               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17848                   *p++ = PieceToChar(piece);
17849         }
17850
17851         if( q == p ) *p++ = '-';
17852         *p++ = ']';
17853         *p++ = ' ';
17854     }
17855
17856     /* Active color */
17857     *p++ = whiteToPlay ? 'w' : 'b';
17858     *p++ = ' ';
17859
17860   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17861     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17862   } else {
17863   if(nrCastlingRights) {
17864      int handW=0, handB=0;
17865      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17866         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17867         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17868      }
17869      q = p;
17870      if(appData.fischerCastling) {
17871         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17872            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17873                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17874         } else {
17875        /* [HGM] write directly from rights */
17876            if(boards[move][CASTLING][2] != NoRights &&
17877               boards[move][CASTLING][0] != NoRights   )
17878                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17879            if(boards[move][CASTLING][2] != NoRights &&
17880               boards[move][CASTLING][1] != NoRights   )
17881                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17882         }
17883         if(handB) {
17884            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17885                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17886         } else {
17887            if(boards[move][CASTLING][5] != NoRights &&
17888               boards[move][CASTLING][3] != NoRights   )
17889                 *p++ = boards[move][CASTLING][3] + AAA;
17890            if(boards[move][CASTLING][5] != NoRights &&
17891               boards[move][CASTLING][4] != NoRights   )
17892                 *p++ = boards[move][CASTLING][4] + AAA;
17893         }
17894      } else {
17895
17896         /* [HGM] write true castling rights */
17897         if( nrCastlingRights == 6 ) {
17898             int q, k=0;
17899             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17900                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17901             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17902                  boards[move][CASTLING][2] != NoRights  );
17903             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17904                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17905                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17906                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17907             }
17908             if(q) *p++ = 'Q';
17909             k = 0;
17910             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17911                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17912             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17913                  boards[move][CASTLING][5] != NoRights  );
17914             if(handB) {
17915                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17916                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17917                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17918             }
17919             if(q) *p++ = 'q';
17920         }
17921      }
17922      if (q == p) *p++ = '-'; /* No castling rights */
17923      *p++ = ' ';
17924   }
17925
17926   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17927      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17928      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17929     /* En passant target square */
17930     if (move > backwardMostMove) {
17931         fromX = moveList[move - 1][0] - AAA;
17932         fromY = moveList[move - 1][1] - ONE;
17933         toX = moveList[move - 1][2] - AAA;
17934         toY = moveList[move - 1][3] - ONE;
17935         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17936             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17937             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17938             fromX == toX) {
17939             /* 2-square pawn move just happened */
17940             *p++ = toX + AAA;
17941             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17942         } else {
17943             *p++ = '-';
17944         }
17945     } else if(move == backwardMostMove) {
17946         // [HGM] perhaps we should always do it like this, and forget the above?
17947         if((signed char)boards[move][EP_STATUS] >= 0) {
17948             *p++ = boards[move][EP_STATUS] + AAA;
17949             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17950         } else {
17951             *p++ = '-';
17952         }
17953     } else {
17954         *p++ = '-';
17955     }
17956     *p++ = ' ';
17957   }
17958   }
17959
17960     if(moveCounts)
17961     {   int i = 0, j=move;
17962
17963         /* [HGM] find reversible plies */
17964         if (appData.debugMode) { int k;
17965             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17966             for(k=backwardMostMove; k<=forwardMostMove; k++)
17967                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17968
17969         }
17970
17971         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17972         if( j == backwardMostMove ) i += initialRulePlies;
17973         sprintf(p, "%d ", i);
17974         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17975
17976         /* Fullmove number */
17977         sprintf(p, "%d", (move / 2) + 1);
17978     } else *--p = NULLCHAR;
17979
17980     return StrSave(buf);
17981 }
17982
17983 Boolean
17984 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17985 {
17986     int i, j, k, w=0, subst=0, shuffle=0;
17987     char *p, c;
17988     int emptycount, virgin[BOARD_FILES];
17989     ChessSquare piece;
17990
17991     p = fen;
17992
17993     /* Piece placement data */
17994     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17995         j = 0;
17996         for (;;) {
17997             if (*p == '/' || *p == ' ' || *p == '[' ) {
17998                 if(j > w) w = j;
17999                 emptycount = gameInfo.boardWidth - j;
18000                 while (emptycount--)
18001                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18002                 if (*p == '/') p++;
18003                 else if(autoSize) { // we stumbled unexpectedly into end of board
18004                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18005                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18006                     }
18007                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18008                 }
18009                 break;
18010 #if(BOARD_FILES >= 10)*0
18011             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18012                 p++; emptycount=10;
18013                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18014                 while (emptycount--)
18015                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18016 #endif
18017             } else if (*p == '*') {
18018                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18019             } else if (isdigit(*p)) {
18020                 emptycount = *p++ - '0';
18021                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18022                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18023                 while (emptycount--)
18024                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18025             } else if (*p == '<') {
18026                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18027                 else if (i != 0 || !shuffle) return FALSE;
18028                 p++;
18029             } else if (shuffle && *p == '>') {
18030                 p++; // for now ignore closing shuffle range, and assume rank-end
18031             } else if (*p == '?') {
18032                 if (j >= gameInfo.boardWidth) return FALSE;
18033                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18034                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18035             } else if (*p == '+' || isalpha(*p)) {
18036                 if (j >= gameInfo.boardWidth) return FALSE;
18037                 if(*p=='+') {
18038                     piece = CharToPiece(*++p);
18039                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18040                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18041                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18042                 } else piece = CharToPiece(*p++);
18043
18044                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18045                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18046                     piece = (ChessSquare) (PROMOTED piece);
18047                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18048                     p++;
18049                 }
18050                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18051             } else {
18052                 return FALSE;
18053             }
18054         }
18055     }
18056     while (*p == '/' || *p == ' ') p++;
18057
18058     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18059
18060     /* [HGM] by default clear Crazyhouse holdings, if present */
18061     if(gameInfo.holdingsWidth) {
18062        for(i=0; i<BOARD_HEIGHT; i++) {
18063            board[i][0]             = EmptySquare; /* black holdings */
18064            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18065            board[i][1]             = (ChessSquare) 0; /* black counts */
18066            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18067        }
18068     }
18069
18070     /* [HGM] look for Crazyhouse holdings here */
18071     while(*p==' ') p++;
18072     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18073         int swap=0, wcnt=0, bcnt=0;
18074         if(*p == '[') p++;
18075         if(*p == '<') swap++, p++;
18076         if(*p == '-' ) p++; /* empty holdings */ else {
18077             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18078             /* if we would allow FEN reading to set board size, we would   */
18079             /* have to add holdings and shift the board read so far here   */
18080             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18081                 p++;
18082                 if((int) piece >= (int) BlackPawn ) {
18083                     i = (int)piece - (int)BlackPawn;
18084                     i = PieceToNumber((ChessSquare)i);
18085                     if( i >= gameInfo.holdingsSize ) return FALSE;
18086                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18087                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18088                     bcnt++;
18089                 } else {
18090                     i = (int)piece - (int)WhitePawn;
18091                     i = PieceToNumber((ChessSquare)i);
18092                     if( i >= gameInfo.holdingsSize ) return FALSE;
18093                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18094                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18095                     wcnt++;
18096                 }
18097             }
18098             if(subst) { // substitute back-rank question marks by holdings pieces
18099                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18100                     int k, m, n = bcnt + 1;
18101                     if(board[0][j] == ClearBoard) {
18102                         if(!wcnt) return FALSE;
18103                         n = rand() % wcnt;
18104                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18105                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18106                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18107                             break;
18108                         }
18109                     }
18110                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18111                         if(!bcnt) return FALSE;
18112                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18113                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18114                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18115                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18116                             break;
18117                         }
18118                     }
18119                 }
18120                 subst = 0;
18121             }
18122         }
18123         if(*p == ']') p++;
18124     }
18125
18126     if(subst) return FALSE; // substitution requested, but no holdings
18127
18128     while(*p == ' ') p++;
18129
18130     /* Active color */
18131     c = *p++;
18132     if(appData.colorNickNames) {
18133       if( c == appData.colorNickNames[0] ) c = 'w'; else
18134       if( c == appData.colorNickNames[1] ) c = 'b';
18135     }
18136     switch (c) {
18137       case 'w':
18138         *blackPlaysFirst = FALSE;
18139         break;
18140       case 'b':
18141         *blackPlaysFirst = TRUE;
18142         break;
18143       default:
18144         return FALSE;
18145     }
18146
18147     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18148     /* return the extra info in global variiables             */
18149
18150     /* set defaults in case FEN is incomplete */
18151     board[EP_STATUS] = EP_UNKNOWN;
18152     for(i=0; i<nrCastlingRights; i++ ) {
18153         board[CASTLING][i] =
18154             appData.fischerCastling ? NoRights : initialRights[i];
18155     }   /* assume possible unless obviously impossible */
18156     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18157     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18158     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18159                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18160     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18161     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18162     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18163                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18164     FENrulePlies = 0;
18165
18166     while(*p==' ') p++;
18167     if(nrCastlingRights) {
18168       int fischer = 0;
18169       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18170       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18171           /* castling indicator present, so default becomes no castlings */
18172           for(i=0; i<nrCastlingRights; i++ ) {
18173                  board[CASTLING][i] = NoRights;
18174           }
18175       }
18176       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18177              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18178              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18179              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18180         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18181
18182         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18183             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18184             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18185         }
18186         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18187             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18188         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18189                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18190         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18191                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18192         switch(c) {
18193           case'K':
18194               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18195               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18196               board[CASTLING][2] = whiteKingFile;
18197               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18198               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18199               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18200               break;
18201           case'Q':
18202               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18203               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18204               board[CASTLING][2] = whiteKingFile;
18205               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18206               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18207               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18208               break;
18209           case'k':
18210               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18211               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18212               board[CASTLING][5] = blackKingFile;
18213               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18214               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18215               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18216               break;
18217           case'q':
18218               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18219               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18220               board[CASTLING][5] = blackKingFile;
18221               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18222               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18223               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18224           case '-':
18225               break;
18226           default: /* FRC castlings */
18227               if(c >= 'a') { /* black rights */
18228                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18229                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18230                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18231                   if(i == BOARD_RGHT) break;
18232                   board[CASTLING][5] = i;
18233                   c -= AAA;
18234                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18235                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18236                   if(c > i)
18237                       board[CASTLING][3] = c;
18238                   else
18239                       board[CASTLING][4] = c;
18240               } else { /* white rights */
18241                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18242                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18243                     if(board[0][i] == WhiteKing) break;
18244                   if(i == BOARD_RGHT) break;
18245                   board[CASTLING][2] = i;
18246                   c -= AAA - 'a' + 'A';
18247                   if(board[0][c] >= WhiteKing) break;
18248                   if(c > i)
18249                       board[CASTLING][0] = c;
18250                   else
18251                       board[CASTLING][1] = c;
18252               }
18253         }
18254       }
18255       for(i=0; i<nrCastlingRights; i++)
18256         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18257       if(gameInfo.variant == VariantSChess)
18258         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18259       if(fischer && shuffle) appData.fischerCastling = TRUE;
18260     if (appData.debugMode) {
18261         fprintf(debugFP, "FEN castling rights:");
18262         for(i=0; i<nrCastlingRights; i++)
18263         fprintf(debugFP, " %d", board[CASTLING][i]);
18264         fprintf(debugFP, "\n");
18265     }
18266
18267       while(*p==' ') p++;
18268     }
18269
18270     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18271
18272     /* read e.p. field in games that know e.p. capture */
18273     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18274        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18275        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18276       if(*p=='-') {
18277         p++; board[EP_STATUS] = EP_NONE;
18278       } else {
18279          char c = *p++ - AAA;
18280
18281          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18282          if(*p >= '0' && *p <='9') p++;
18283          board[EP_STATUS] = c;
18284       }
18285     }
18286
18287
18288     if(sscanf(p, "%d", &i) == 1) {
18289         FENrulePlies = i; /* 50-move ply counter */
18290         /* (The move number is still ignored)    */
18291     }
18292
18293     return TRUE;
18294 }
18295
18296 void
18297 EditPositionPasteFEN (char *fen)
18298 {
18299   if (fen != NULL) {
18300     Board initial_position;
18301
18302     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18303       DisplayError(_("Bad FEN position in clipboard"), 0);
18304       return ;
18305     } else {
18306       int savedBlackPlaysFirst = blackPlaysFirst;
18307       EditPositionEvent();
18308       blackPlaysFirst = savedBlackPlaysFirst;
18309       CopyBoard(boards[0], initial_position);
18310       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18311       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18312       DisplayBothClocks();
18313       DrawPosition(FALSE, boards[currentMove]);
18314     }
18315   }
18316 }
18317
18318 static char cseq[12] = "\\   ";
18319
18320 Boolean
18321 set_cont_sequence (char *new_seq)
18322 {
18323     int len;
18324     Boolean ret;
18325
18326     // handle bad attempts to set the sequence
18327         if (!new_seq)
18328                 return 0; // acceptable error - no debug
18329
18330     len = strlen(new_seq);
18331     ret = (len > 0) && (len < sizeof(cseq));
18332     if (ret)
18333       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18334     else if (appData.debugMode)
18335       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18336     return ret;
18337 }
18338
18339 /*
18340     reformat a source message so words don't cross the width boundary.  internal
18341     newlines are not removed.  returns the wrapped size (no null character unless
18342     included in source message).  If dest is NULL, only calculate the size required
18343     for the dest buffer.  lp argument indicats line position upon entry, and it's
18344     passed back upon exit.
18345 */
18346 int
18347 wrap (char *dest, char *src, int count, int width, int *lp)
18348 {
18349     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18350
18351     cseq_len = strlen(cseq);
18352     old_line = line = *lp;
18353     ansi = len = clen = 0;
18354
18355     for (i=0; i < count; i++)
18356     {
18357         if (src[i] == '\033')
18358             ansi = 1;
18359
18360         // if we hit the width, back up
18361         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18362         {
18363             // store i & len in case the word is too long
18364             old_i = i, old_len = len;
18365
18366             // find the end of the last word
18367             while (i && src[i] != ' ' && src[i] != '\n')
18368             {
18369                 i--;
18370                 len--;
18371             }
18372
18373             // word too long?  restore i & len before splitting it
18374             if ((old_i-i+clen) >= width)
18375             {
18376                 i = old_i;
18377                 len = old_len;
18378             }
18379
18380             // extra space?
18381             if (i && src[i-1] == ' ')
18382                 len--;
18383
18384             if (src[i] != ' ' && src[i] != '\n')
18385             {
18386                 i--;
18387                 if (len)
18388                     len--;
18389             }
18390
18391             // now append the newline and continuation sequence
18392             if (dest)
18393                 dest[len] = '\n';
18394             len++;
18395             if (dest)
18396                 strncpy(dest+len, cseq, cseq_len);
18397             len += cseq_len;
18398             line = cseq_len;
18399             clen = cseq_len;
18400             continue;
18401         }
18402
18403         if (dest)
18404             dest[len] = src[i];
18405         len++;
18406         if (!ansi)
18407             line++;
18408         if (src[i] == '\n')
18409             line = 0;
18410         if (src[i] == 'm')
18411             ansi = 0;
18412     }
18413     if (dest && appData.debugMode)
18414     {
18415         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18416             count, width, line, len, *lp);
18417         show_bytes(debugFP, src, count);
18418         fprintf(debugFP, "\ndest: ");
18419         show_bytes(debugFP, dest, len);
18420         fprintf(debugFP, "\n");
18421     }
18422     *lp = dest ? line : old_line;
18423
18424     return len;
18425 }
18426
18427 // [HGM] vari: routines for shelving variations
18428 Boolean modeRestore = FALSE;
18429
18430 void
18431 PushInner (int firstMove, int lastMove)
18432 {
18433         int i, j, nrMoves = lastMove - firstMove;
18434
18435         // push current tail of game on stack
18436         savedResult[storedGames] = gameInfo.result;
18437         savedDetails[storedGames] = gameInfo.resultDetails;
18438         gameInfo.resultDetails = NULL;
18439         savedFirst[storedGames] = firstMove;
18440         savedLast [storedGames] = lastMove;
18441         savedFramePtr[storedGames] = framePtr;
18442         framePtr -= nrMoves; // reserve space for the boards
18443         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18444             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18445             for(j=0; j<MOVE_LEN; j++)
18446                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18447             for(j=0; j<2*MOVE_LEN; j++)
18448                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18449             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18450             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18451             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18452             pvInfoList[firstMove+i-1].depth = 0;
18453             commentList[framePtr+i] = commentList[firstMove+i];
18454             commentList[firstMove+i] = NULL;
18455         }
18456
18457         storedGames++;
18458         forwardMostMove = firstMove; // truncate game so we can start variation
18459 }
18460
18461 void
18462 PushTail (int firstMove, int lastMove)
18463 {
18464         if(appData.icsActive) { // only in local mode
18465                 forwardMostMove = currentMove; // mimic old ICS behavior
18466                 return;
18467         }
18468         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18469
18470         PushInner(firstMove, lastMove);
18471         if(storedGames == 1) GreyRevert(FALSE);
18472         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18473 }
18474
18475 void
18476 PopInner (Boolean annotate)
18477 {
18478         int i, j, nrMoves;
18479         char buf[8000], moveBuf[20];
18480
18481         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18482         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18483         nrMoves = savedLast[storedGames] - currentMove;
18484         if(annotate) {
18485                 int cnt = 10;
18486                 if(!WhiteOnMove(currentMove))
18487                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18488                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18489                 for(i=currentMove; i<forwardMostMove; i++) {
18490                         if(WhiteOnMove(i))
18491                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18492                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18493                         strcat(buf, moveBuf);
18494                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18495                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18496                 }
18497                 strcat(buf, ")");
18498         }
18499         for(i=1; i<=nrMoves; i++) { // copy last variation back
18500             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18501             for(j=0; j<MOVE_LEN; j++)
18502                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18503             for(j=0; j<2*MOVE_LEN; j++)
18504                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18505             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18506             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18507             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18508             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18509             commentList[currentMove+i] = commentList[framePtr+i];
18510             commentList[framePtr+i] = NULL;
18511         }
18512         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18513         framePtr = savedFramePtr[storedGames];
18514         gameInfo.result = savedResult[storedGames];
18515         if(gameInfo.resultDetails != NULL) {
18516             free(gameInfo.resultDetails);
18517       }
18518         gameInfo.resultDetails = savedDetails[storedGames];
18519         forwardMostMove = currentMove + nrMoves;
18520 }
18521
18522 Boolean
18523 PopTail (Boolean annotate)
18524 {
18525         if(appData.icsActive) return FALSE; // only in local mode
18526         if(!storedGames) return FALSE; // sanity
18527         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18528
18529         PopInner(annotate);
18530         if(currentMove < forwardMostMove) ForwardEvent(); else
18531         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18532
18533         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18534         return TRUE;
18535 }
18536
18537 void
18538 CleanupTail ()
18539 {       // remove all shelved variations
18540         int i;
18541         for(i=0; i<storedGames; i++) {
18542             if(savedDetails[i])
18543                 free(savedDetails[i]);
18544             savedDetails[i] = NULL;
18545         }
18546         for(i=framePtr; i<MAX_MOVES; i++) {
18547                 if(commentList[i]) free(commentList[i]);
18548                 commentList[i] = NULL;
18549         }
18550         framePtr = MAX_MOVES-1;
18551         storedGames = 0;
18552 }
18553
18554 void
18555 LoadVariation (int index, char *text)
18556 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18557         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18558         int level = 0, move;
18559
18560         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18561         // first find outermost bracketing variation
18562         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18563             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18564                 if(*p == '{') wait = '}'; else
18565                 if(*p == '[') wait = ']'; else
18566                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18567                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18568             }
18569             if(*p == wait) wait = NULLCHAR; // closing ]} found
18570             p++;
18571         }
18572         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18573         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18574         end[1] = NULLCHAR; // clip off comment beyond variation
18575         ToNrEvent(currentMove-1);
18576         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18577         // kludge: use ParsePV() to append variation to game
18578         move = currentMove;
18579         ParsePV(start, TRUE, TRUE);
18580         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18581         ClearPremoveHighlights();
18582         CommentPopDown();
18583         ToNrEvent(currentMove+1);
18584 }
18585
18586 void
18587 LoadTheme ()
18588 {
18589     char *p, *q, buf[MSG_SIZ];
18590     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18591         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18592         ParseArgsFromString(buf);
18593         ActivateTheme(TRUE); // also redo colors
18594         return;
18595     }
18596     p = nickName;
18597     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18598     {
18599         int len;
18600         q = appData.themeNames;
18601         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18602       if(appData.useBitmaps) {
18603         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18604                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18605                 appData.liteBackTextureMode,
18606                 appData.darkBackTextureMode );
18607       } else {
18608         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18609                 Col2Text(2),   // lightSquareColor
18610                 Col2Text(3) ); // darkSquareColor
18611       }
18612       if(appData.useBorder) {
18613         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18614                 appData.border);
18615       } else {
18616         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18617       }
18618       if(appData.useFont) {
18619         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18620                 appData.renderPiecesWithFont,
18621                 appData.fontToPieceTable,
18622                 Col2Text(9),    // appData.fontBackColorWhite
18623                 Col2Text(10) ); // appData.fontForeColorBlack
18624       } else {
18625         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18626                 appData.pieceDirectory);
18627         if(!appData.pieceDirectory[0])
18628           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18629                 Col2Text(0),   // whitePieceColor
18630                 Col2Text(1) ); // blackPieceColor
18631       }
18632       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18633                 Col2Text(4),   // highlightSquareColor
18634                 Col2Text(5) ); // premoveHighlightColor
18635         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18636         if(insert != q) insert[-1] = NULLCHAR;
18637         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18638         if(q)   free(q);
18639     }
18640     ActivateTheme(FALSE);
18641 }