758b71a2be21dc2277b003518807f7b0ee399872
[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 static Boolean pieceDefs;
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.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 VariantXiangqi:    /* [HGM] repetition rules not implemented */
1205       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1206       case VariantGothic:     /* [HGM] should work */
1207       case VariantCapablanca: /* [HGM] should work */
1208       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1209       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1210       case VariantChu:        /* [HGM] experimental */
1211       case VariantKnightmate: /* [HGM] should work */
1212       case VariantCylinder:   /* [HGM] untested */
1213       case VariantFalcon:     /* [HGM] untested */
1214       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1215                                  offboard interposition not understood */
1216       case VariantNormal:     /* definitely works! */
1217       case VariantWildCastle: /* pieces not automatically shuffled */
1218       case VariantNoCastle:   /* pieces not automatically shuffled */
1219       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1220       case VariantLosers:     /* should work except for win condition,
1221                                  and doesn't know captures are mandatory */
1222       case VariantSuicide:    /* should work except for win condition,
1223                                  and doesn't know captures are mandatory */
1224       case VariantGiveaway:   /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantTwoKings:   /* should work */
1227       case VariantAtomic:     /* should work except for win condition */
1228       case Variant3Check:     /* should work except for win condition */
1229       case VariantShatranj:   /* should work except for all win conditions */
1230       case VariantMakruk:     /* should work except for draw countdown */
1231       case VariantASEAN :     /* should work except for draw countdown */
1232       case VariantBerolina:   /* might work if TestLegality is off */
1233       case VariantCapaRandom: /* should work */
1234       case VariantJanus:      /* should work */
1235       case VariantSuper:      /* experimental */
1236       case VariantGreat:      /* experimental, requires legality testing to be off */
1237       case VariantSChess:     /* S-Chess, should work */
1238       case VariantGrand:      /* should work */
1239       case VariantSpartan:    /* should work */
1240       case VariantLion:       /* should work */
1241       case VariantChuChess:   /* should work */
1242         break;
1243       }
1244     }
1245
1246 }
1247
1248 int
1249 NextIntegerFromString (char ** str, long * value)
1250 {
1251     int result = -1;
1252     char * s = *str;
1253
1254     while( *s == ' ' || *s == '\t' ) {
1255         s++;
1256     }
1257
1258     *value = 0;
1259
1260     if( *s >= '0' && *s <= '9' ) {
1261         while( *s >= '0' && *s <= '9' ) {
1262             *value = *value * 10 + (*s - '0');
1263             s++;
1264         }
1265
1266         result = 0;
1267     }
1268
1269     *str = s;
1270
1271     return result;
1272 }
1273
1274 int
1275 NextTimeControlFromString (char ** str, long * value)
1276 {
1277     long temp;
1278     int result = NextIntegerFromString( str, &temp );
1279
1280     if( result == 0 ) {
1281         *value = temp * 60; /* Minutes */
1282         if( **str == ':' ) {
1283             (*str)++;
1284             result = NextIntegerFromString( str, &temp );
1285             *value += temp; /* Seconds */
1286         }
1287     }
1288
1289     return result;
1290 }
1291
1292 int
1293 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1294 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1295     int result = -1, type = 0; long temp, temp2;
1296
1297     if(**str != ':') return -1; // old params remain in force!
1298     (*str)++;
1299     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1300     if( NextIntegerFromString( str, &temp ) ) return -1;
1301     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1302
1303     if(**str != '/') {
1304         /* time only: incremental or sudden-death time control */
1305         if(**str == '+') { /* increment follows; read it */
1306             (*str)++;
1307             if(**str == '!') type = *(*str)++; // Bronstein TC
1308             if(result = NextIntegerFromString( str, &temp2)) return -1;
1309             *inc = temp2 * 1000;
1310             if(**str == '.') { // read fraction of increment
1311                 char *start = ++(*str);
1312                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1313                 temp2 *= 1000;
1314                 while(start++ < *str) temp2 /= 10;
1315                 *inc += temp2;
1316             }
1317         } else *inc = 0;
1318         *moves = 0; *tc = temp * 1000; *incType = type;
1319         return 0;
1320     }
1321
1322     (*str)++; /* classical time control */
1323     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1324
1325     if(result == 0) {
1326         *moves = temp;
1327         *tc    = temp2 * 1000;
1328         *inc   = 0;
1329         *incType = type;
1330     }
1331     return result;
1332 }
1333
1334 int
1335 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1336 {   /* [HGM] get time to add from the multi-session time-control string */
1337     int incType, moves=1; /* kludge to force reading of first session */
1338     long time, increment;
1339     char *s = tcString;
1340
1341     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1342     do {
1343         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1344         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1345         if(movenr == -1) return time;    /* last move before new session     */
1346         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1347         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1348         if(!moves) return increment;     /* current session is incremental   */
1349         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1350     } while(movenr >= -1);               /* try again for next session       */
1351
1352     return 0; // no new time quota on this move
1353 }
1354
1355 int
1356 ParseTimeControl (char *tc, float ti, int mps)
1357 {
1358   long tc1;
1359   long tc2;
1360   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1361   int min, sec=0;
1362
1363   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1364   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1365       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1366   if(ti > 0) {
1367
1368     if(mps)
1369       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1370     else
1371       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1372   } else {
1373     if(mps)
1374       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1375     else
1376       snprintf(buf, MSG_SIZ, ":%s", mytc);
1377   }
1378   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1379
1380   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1381     return FALSE;
1382   }
1383
1384   if( *tc == '/' ) {
1385     /* Parse second time control */
1386     tc++;
1387
1388     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1389       return FALSE;
1390     }
1391
1392     if( tc2 == 0 ) {
1393       return FALSE;
1394     }
1395
1396     timeControl_2 = tc2 * 1000;
1397   }
1398   else {
1399     timeControl_2 = 0;
1400   }
1401
1402   if( tc1 == 0 ) {
1403     return FALSE;
1404   }
1405
1406   timeControl = tc1 * 1000;
1407
1408   if (ti >= 0) {
1409     timeIncrement = ti * 1000;  /* convert to ms */
1410     movesPerSession = 0;
1411   } else {
1412     timeIncrement = 0;
1413     movesPerSession = mps;
1414   }
1415   return TRUE;
1416 }
1417
1418 void
1419 InitBackEnd2 ()
1420 {
1421     if (appData.debugMode) {
1422 #    ifdef __GIT_VERSION
1423       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1424 #    else
1425       fprintf(debugFP, "Version: %s\n", programVersion);
1426 #    endif
1427     }
1428     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1429
1430     set_cont_sequence(appData.wrapContSeq);
1431     if (appData.matchGames > 0) {
1432         appData.matchMode = TRUE;
1433     } else if (appData.matchMode) {
1434         appData.matchGames = 1;
1435     }
1436     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1437         appData.matchGames = appData.sameColorGames;
1438     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1439         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1440         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1441     }
1442     Reset(TRUE, FALSE);
1443     if (appData.noChessProgram || first.protocolVersion == 1) {
1444       InitBackEnd3();
1445     } else {
1446       /* kludge: allow timeout for initial "feature" commands */
1447       FreezeUI();
1448       DisplayMessage("", _("Starting chess program"));
1449       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1450     }
1451 }
1452
1453 int
1454 CalculateIndex (int index, int gameNr)
1455 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1456     int res;
1457     if(index > 0) return index; // fixed nmber
1458     if(index == 0) return 1;
1459     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1460     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1461     return res;
1462 }
1463
1464 int
1465 LoadGameOrPosition (int gameNr)
1466 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1467     if (*appData.loadGameFile != NULLCHAR) {
1468         if (!LoadGameFromFile(appData.loadGameFile,
1469                 CalculateIndex(appData.loadGameIndex, gameNr),
1470                               appData.loadGameFile, FALSE)) {
1471             DisplayFatalError(_("Bad game file"), 0, 1);
1472             return 0;
1473         }
1474     } else if (*appData.loadPositionFile != NULLCHAR) {
1475         if (!LoadPositionFromFile(appData.loadPositionFile,
1476                 CalculateIndex(appData.loadPositionIndex, gameNr),
1477                                   appData.loadPositionFile)) {
1478             DisplayFatalError(_("Bad position file"), 0, 1);
1479             return 0;
1480         }
1481     }
1482     return 1;
1483 }
1484
1485 void
1486 ReserveGame (int gameNr, char resChar)
1487 {
1488     FILE *tf = fopen(appData.tourneyFile, "r+");
1489     char *p, *q, c, buf[MSG_SIZ];
1490     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1491     safeStrCpy(buf, lastMsg, MSG_SIZ);
1492     DisplayMessage(_("Pick new game"), "");
1493     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1494     ParseArgsFromFile(tf);
1495     p = q = appData.results;
1496     if(appData.debugMode) {
1497       char *r = appData.participants;
1498       fprintf(debugFP, "results = '%s'\n", p);
1499       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1500       fprintf(debugFP, "\n");
1501     }
1502     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1503     nextGame = q - p;
1504     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1505     safeStrCpy(q, p, strlen(p) + 2);
1506     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1507     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1508     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1509         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1510         q[nextGame] = '*';
1511     }
1512     fseek(tf, -(strlen(p)+4), SEEK_END);
1513     c = fgetc(tf);
1514     if(c != '"') // depending on DOS or Unix line endings we can be one off
1515          fseek(tf, -(strlen(p)+2), SEEK_END);
1516     else fseek(tf, -(strlen(p)+3), SEEK_END);
1517     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1518     DisplayMessage(buf, "");
1519     free(p); appData.results = q;
1520     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1521        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1522       int round = appData.defaultMatchGames * appData.tourneyType;
1523       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1524          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1525         UnloadEngine(&first);  // next game belongs to other pairing;
1526         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1527     }
1528     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1529 }
1530
1531 void
1532 MatchEvent (int mode)
1533 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1534         int dummy;
1535         if(matchMode) { // already in match mode: switch it off
1536             abortMatch = TRUE;
1537             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1538             return;
1539         }
1540 //      if(gameMode != BeginningOfGame) {
1541 //          DisplayError(_("You can only start a match from the initial position."), 0);
1542 //          return;
1543 //      }
1544         abortMatch = FALSE;
1545         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1546         /* Set up machine vs. machine match */
1547         nextGame = 0;
1548         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1549         if(appData.tourneyFile[0]) {
1550             ReserveGame(-1, 0);
1551             if(nextGame > appData.matchGames) {
1552                 char buf[MSG_SIZ];
1553                 if(strchr(appData.results, '*') == NULL) {
1554                     FILE *f;
1555                     appData.tourneyCycles++;
1556                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1557                         fclose(f);
1558                         NextTourneyGame(-1, &dummy);
1559                         ReserveGame(-1, 0);
1560                         if(nextGame <= appData.matchGames) {
1561                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1562                             matchMode = mode;
1563                             ScheduleDelayedEvent(NextMatchGame, 10000);
1564                             return;
1565                         }
1566                     }
1567                 }
1568                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1569                 DisplayError(buf, 0);
1570                 appData.tourneyFile[0] = 0;
1571                 return;
1572             }
1573         } else
1574         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1575             DisplayFatalError(_("Can't have a match with no chess programs"),
1576                               0, 2);
1577             return;
1578         }
1579         matchMode = mode;
1580         matchGame = roundNr = 1;
1581         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1582         NextMatchGame();
1583 }
1584
1585 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1586
1587 void
1588 InitBackEnd3 P((void))
1589 {
1590     GameMode initialMode;
1591     char buf[MSG_SIZ];
1592     int err, len;
1593
1594     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1595        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1596         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1597        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1598        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1599         char c, *q = first.variants, *p = strchr(q, ',');
1600         if(p) *p = NULLCHAR;
1601         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1602             int w, h, s;
1603             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1604                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1605             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1606             Reset(TRUE, FALSE);         // and re-initialize
1607         }
1608         if(p) *p = ',';
1609     }
1610
1611     InitChessProgram(&first, startedFromSetupPosition);
1612
1613     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1614         free(programVersion);
1615         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1616         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1617         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1618     }
1619
1620     if (appData.icsActive) {
1621 #ifdef WIN32
1622         /* [DM] Make a console window if needed [HGM] merged ifs */
1623         ConsoleCreate();
1624 #endif
1625         err = establish();
1626         if (err != 0)
1627           {
1628             if (*appData.icsCommPort != NULLCHAR)
1629               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1630                              appData.icsCommPort);
1631             else
1632               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1633                         appData.icsHost, appData.icsPort);
1634
1635             if( (len >= MSG_SIZ) && appData.debugMode )
1636               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1637
1638             DisplayFatalError(buf, err, 1);
1639             return;
1640         }
1641         SetICSMode();
1642         telnetISR =
1643           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1644         fromUserISR =
1645           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1646         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1647             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1648     } else if (appData.noChessProgram) {
1649         SetNCPMode();
1650     } else {
1651         SetGNUMode();
1652     }
1653
1654     if (*appData.cmailGameName != NULLCHAR) {
1655         SetCmailMode();
1656         OpenLoopback(&cmailPR);
1657         cmailISR =
1658           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1659     }
1660
1661     ThawUI();
1662     DisplayMessage("", "");
1663     if (StrCaseCmp(appData.initialMode, "") == 0) {
1664       initialMode = BeginningOfGame;
1665       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1666         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1667         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1668         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1669         ModeHighlight();
1670       }
1671     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1672       initialMode = TwoMachinesPlay;
1673     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1674       initialMode = AnalyzeFile;
1675     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1676       initialMode = AnalyzeMode;
1677     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1678       initialMode = MachinePlaysWhite;
1679     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1680       initialMode = MachinePlaysBlack;
1681     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1682       initialMode = EditGame;
1683     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1684       initialMode = EditPosition;
1685     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1686       initialMode = Training;
1687     } else {
1688       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1689       if( (len >= MSG_SIZ) && appData.debugMode )
1690         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1691
1692       DisplayFatalError(buf, 0, 2);
1693       return;
1694     }
1695
1696     if (appData.matchMode) {
1697         if(appData.tourneyFile[0]) { // start tourney from command line
1698             FILE *f;
1699             if(f = fopen(appData.tourneyFile, "r")) {
1700                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1701                 fclose(f);
1702                 appData.clockMode = TRUE;
1703                 SetGNUMode();
1704             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1705         }
1706         MatchEvent(TRUE);
1707     } else if (*appData.cmailGameName != NULLCHAR) {
1708         /* Set up cmail mode */
1709         ReloadCmailMsgEvent(TRUE);
1710     } else {
1711         /* Set up other modes */
1712         if (initialMode == AnalyzeFile) {
1713           if (*appData.loadGameFile == NULLCHAR) {
1714             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1715             return;
1716           }
1717         }
1718         if (*appData.loadGameFile != NULLCHAR) {
1719             (void) LoadGameFromFile(appData.loadGameFile,
1720                                     appData.loadGameIndex,
1721                                     appData.loadGameFile, TRUE);
1722         } else if (*appData.loadPositionFile != NULLCHAR) {
1723             (void) LoadPositionFromFile(appData.loadPositionFile,
1724                                         appData.loadPositionIndex,
1725                                         appData.loadPositionFile);
1726             /* [HGM] try to make self-starting even after FEN load */
1727             /* to allow automatic setup of fairy variants with wtm */
1728             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1729                 gameMode = BeginningOfGame;
1730                 setboardSpoiledMachineBlack = 1;
1731             }
1732             /* [HGM] loadPos: make that every new game uses the setup */
1733             /* from file as long as we do not switch variant          */
1734             if(!blackPlaysFirst) {
1735                 startedFromPositionFile = TRUE;
1736                 CopyBoard(filePosition, boards[0]);
1737             }
1738         }
1739         if (initialMode == AnalyzeMode) {
1740           if (appData.noChessProgram) {
1741             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1742             return;
1743           }
1744           if (appData.icsActive) {
1745             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1746             return;
1747           }
1748           AnalyzeModeEvent();
1749         } else if (initialMode == AnalyzeFile) {
1750           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1751           ShowThinkingEvent();
1752           AnalyzeFileEvent();
1753           AnalysisPeriodicEvent(1);
1754         } else if (initialMode == MachinePlaysWhite) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1757                               0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1762                               0, 2);
1763             return;
1764           }
1765           MachineWhiteEvent();
1766         } else if (initialMode == MachinePlaysBlack) {
1767           if (appData.noChessProgram) {
1768             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1769                               0, 2);
1770             return;
1771           }
1772           if (appData.icsActive) {
1773             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1774                               0, 2);
1775             return;
1776           }
1777           MachineBlackEvent();
1778         } else if (initialMode == TwoMachinesPlay) {
1779           if (appData.noChessProgram) {
1780             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1781                               0, 2);
1782             return;
1783           }
1784           if (appData.icsActive) {
1785             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1786                               0, 2);
1787             return;
1788           }
1789           TwoMachinesEvent();
1790         } else if (initialMode == EditGame) {
1791           EditGameEvent();
1792         } else if (initialMode == EditPosition) {
1793           EditPositionEvent();
1794         } else if (initialMode == Training) {
1795           if (*appData.loadGameFile == NULLCHAR) {
1796             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1797             return;
1798           }
1799           TrainingEvent();
1800         }
1801     }
1802 }
1803
1804 void
1805 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1806 {
1807     DisplayBook(current+1);
1808
1809     MoveHistorySet( movelist, first, last, current, pvInfoList );
1810
1811     EvalGraphSet( first, last, current, pvInfoList );
1812
1813     MakeEngineOutputTitle();
1814 }
1815
1816 /*
1817  * Establish will establish a contact to a remote host.port.
1818  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1819  *  used to talk to the host.
1820  * Returns 0 if okay, error code if not.
1821  */
1822 int
1823 establish ()
1824 {
1825     char buf[MSG_SIZ];
1826
1827     if (*appData.icsCommPort != NULLCHAR) {
1828         /* Talk to the host through a serial comm port */
1829         return OpenCommPort(appData.icsCommPort, &icsPR);
1830
1831     } else if (*appData.gateway != NULLCHAR) {
1832         if (*appData.remoteShell == NULLCHAR) {
1833             /* Use the rcmd protocol to run telnet program on a gateway host */
1834             snprintf(buf, sizeof(buf), "%s %s %s",
1835                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1836             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1837
1838         } else {
1839             /* Use the rsh program to run telnet program on a gateway host */
1840             if (*appData.remoteUser == NULLCHAR) {
1841                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1842                         appData.gateway, appData.telnetProgram,
1843                         appData.icsHost, appData.icsPort);
1844             } else {
1845                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1846                         appData.remoteShell, appData.gateway,
1847                         appData.remoteUser, appData.telnetProgram,
1848                         appData.icsHost, appData.icsPort);
1849             }
1850             return StartChildProcess(buf, "", &icsPR);
1851
1852         }
1853     } else if (appData.useTelnet) {
1854         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1855
1856     } else {
1857         /* TCP socket interface differs somewhat between
1858            Unix and NT; handle details in the front end.
1859            */
1860         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1861     }
1862 }
1863
1864 void
1865 EscapeExpand (char *p, char *q)
1866 {       // [HGM] initstring: routine to shape up string arguments
1867         while(*p++ = *q++) if(p[-1] == '\\')
1868             switch(*q++) {
1869                 case 'n': p[-1] = '\n'; break;
1870                 case 'r': p[-1] = '\r'; break;
1871                 case 't': p[-1] = '\t'; break;
1872                 case '\\': p[-1] = '\\'; break;
1873                 case 0: *p = 0; return;
1874                 default: p[-1] = q[-1]; break;
1875             }
1876 }
1877
1878 void
1879 show_bytes (FILE *fp, char *buf, int count)
1880 {
1881     while (count--) {
1882         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1883             fprintf(fp, "\\%03o", *buf & 0xff);
1884         } else {
1885             putc(*buf, fp);
1886         }
1887         buf++;
1888     }
1889     fflush(fp);
1890 }
1891
1892 /* Returns an errno value */
1893 int
1894 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1895 {
1896     char buf[8192], *p, *q, *buflim;
1897     int left, newcount, outcount;
1898
1899     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1900         *appData.gateway != NULLCHAR) {
1901         if (appData.debugMode) {
1902             fprintf(debugFP, ">ICS: ");
1903             show_bytes(debugFP, message, count);
1904             fprintf(debugFP, "\n");
1905         }
1906         return OutputToProcess(pr, message, count, outError);
1907     }
1908
1909     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1910     p = message;
1911     q = buf;
1912     left = count;
1913     newcount = 0;
1914     while (left) {
1915         if (q >= buflim) {
1916             if (appData.debugMode) {
1917                 fprintf(debugFP, ">ICS: ");
1918                 show_bytes(debugFP, buf, newcount);
1919                 fprintf(debugFP, "\n");
1920             }
1921             outcount = OutputToProcess(pr, buf, newcount, outError);
1922             if (outcount < newcount) return -1; /* to be sure */
1923             q = buf;
1924             newcount = 0;
1925         }
1926         if (*p == '\n') {
1927             *q++ = '\r';
1928             newcount++;
1929         } else if (((unsigned char) *p) == TN_IAC) {
1930             *q++ = (char) TN_IAC;
1931             newcount ++;
1932         }
1933         *q++ = *p++;
1934         newcount++;
1935         left--;
1936     }
1937     if (appData.debugMode) {
1938         fprintf(debugFP, ">ICS: ");
1939         show_bytes(debugFP, buf, newcount);
1940         fprintf(debugFP, "\n");
1941     }
1942     outcount = OutputToProcess(pr, buf, newcount, outError);
1943     if (outcount < newcount) return -1; /* to be sure */
1944     return count;
1945 }
1946
1947 void
1948 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1949 {
1950     int outError, outCount;
1951     static int gotEof = 0;
1952     static FILE *ini;
1953
1954     /* Pass data read from player on to ICS */
1955     if (count > 0) {
1956         gotEof = 0;
1957         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1958         if (outCount < count) {
1959             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1960         }
1961         if(have_sent_ICS_logon == 2) {
1962           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1963             fprintf(ini, "%s", message);
1964             have_sent_ICS_logon = 3;
1965           } else
1966             have_sent_ICS_logon = 1;
1967         } else if(have_sent_ICS_logon == 3) {
1968             fprintf(ini, "%s", message);
1969             fclose(ini);
1970           have_sent_ICS_logon = 1;
1971         }
1972     } else if (count < 0) {
1973         RemoveInputSource(isr);
1974         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1975     } else if (gotEof++ > 0) {
1976         RemoveInputSource(isr);
1977         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1978     }
1979 }
1980
1981 void
1982 KeepAlive ()
1983 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1984     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1985     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1986     SendToICS("date\n");
1987     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1988 }
1989
1990 /* added routine for printf style output to ics */
1991 void
1992 ics_printf (char *format, ...)
1993 {
1994     char buffer[MSG_SIZ];
1995     va_list args;
1996
1997     va_start(args, format);
1998     vsnprintf(buffer, sizeof(buffer), format, args);
1999     buffer[sizeof(buffer)-1] = '\0';
2000     SendToICS(buffer);
2001     va_end(args);
2002 }
2003
2004 void
2005 SendToICS (char *s)
2006 {
2007     int count, outCount, outError;
2008
2009     if (icsPR == NoProc) return;
2010
2011     count = strlen(s);
2012     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2013     if (outCount < count) {
2014         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015     }
2016 }
2017
2018 /* This is used for sending logon scripts to the ICS. Sending
2019    without a delay causes problems when using timestamp on ICC
2020    (at least on my machine). */
2021 void
2022 SendToICSDelayed (char *s, long msdelay)
2023 {
2024     int count, outCount, outError;
2025
2026     if (icsPR == NoProc) return;
2027
2028     count = strlen(s);
2029     if (appData.debugMode) {
2030         fprintf(debugFP, ">ICS: ");
2031         show_bytes(debugFP, s, count);
2032         fprintf(debugFP, "\n");
2033     }
2034     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2035                                       msdelay);
2036     if (outCount < count) {
2037         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2038     }
2039 }
2040
2041
2042 /* Remove all highlighting escape sequences in s
2043    Also deletes any suffix starting with '('
2044    */
2045 char *
2046 StripHighlightAndTitle (char *s)
2047 {
2048     static char retbuf[MSG_SIZ];
2049     char *p = retbuf;
2050
2051     while (*s != NULLCHAR) {
2052         while (*s == '\033') {
2053             while (*s != NULLCHAR && !isalpha(*s)) s++;
2054             if (*s != NULLCHAR) s++;
2055         }
2056         while (*s != NULLCHAR && *s != '\033') {
2057             if (*s == '(' || *s == '[') {
2058                 *p = NULLCHAR;
2059                 return retbuf;
2060             }
2061             *p++ = *s++;
2062         }
2063     }
2064     *p = NULLCHAR;
2065     return retbuf;
2066 }
2067
2068 /* Remove all highlighting escape sequences in s */
2069 char *
2070 StripHighlight (char *s)
2071 {
2072     static char retbuf[MSG_SIZ];
2073     char *p = retbuf;
2074
2075     while (*s != NULLCHAR) {
2076         while (*s == '\033') {
2077             while (*s != NULLCHAR && !isalpha(*s)) s++;
2078             if (*s != NULLCHAR) s++;
2079         }
2080         while (*s != NULLCHAR && *s != '\033') {
2081             *p++ = *s++;
2082         }
2083     }
2084     *p = NULLCHAR;
2085     return retbuf;
2086 }
2087
2088 char engineVariant[MSG_SIZ];
2089 char *variantNames[] = VARIANT_NAMES;
2090 char *
2091 VariantName (VariantClass v)
2092 {
2093     if(v == VariantUnknown || *engineVariant) return engineVariant;
2094     return variantNames[v];
2095 }
2096
2097
2098 /* Identify a variant from the strings the chess servers use or the
2099    PGN Variant tag names we use. */
2100 VariantClass
2101 StringToVariant (char *e)
2102 {
2103     char *p;
2104     int wnum = -1;
2105     VariantClass v = VariantNormal;
2106     int i, found = FALSE;
2107     char buf[MSG_SIZ];
2108     int len;
2109
2110     if (!e) return v;
2111
2112     /* [HGM] skip over optional board-size prefixes */
2113     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2114         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2115         while( *e++ != '_');
2116     }
2117
2118     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2119         v = VariantNormal;
2120         found = TRUE;
2121     } else
2122     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2123       if (p = StrCaseStr(e, variantNames[i])) {
2124         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2125         v = (VariantClass) i;
2126         found = TRUE;
2127         break;
2128       }
2129     }
2130
2131     if (!found) {
2132       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2133           || StrCaseStr(e, "wild/fr")
2134           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2135         v = VariantFischeRandom;
2136       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2137                  (i = 1, p = StrCaseStr(e, "w"))) {
2138         p += i;
2139         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2140         if (isdigit(*p)) {
2141           wnum = atoi(p);
2142         } else {
2143           wnum = -1;
2144         }
2145         switch (wnum) {
2146         case 0: /* FICS only, actually */
2147         case 1:
2148           /* Castling legal even if K starts on d-file */
2149           v = VariantWildCastle;
2150           break;
2151         case 2:
2152         case 3:
2153         case 4:
2154           /* Castling illegal even if K & R happen to start in
2155              normal positions. */
2156           v = VariantNoCastle;
2157           break;
2158         case 5:
2159         case 7:
2160         case 8:
2161         case 10:
2162         case 11:
2163         case 12:
2164         case 13:
2165         case 14:
2166         case 15:
2167         case 18:
2168         case 19:
2169           /* Castling legal iff K & R start in normal positions */
2170           v = VariantNormal;
2171           break;
2172         case 6:
2173         case 20:
2174         case 21:
2175           /* Special wilds for position setup; unclear what to do here */
2176           v = VariantLoadable;
2177           break;
2178         case 9:
2179           /* Bizarre ICC game */
2180           v = VariantTwoKings;
2181           break;
2182         case 16:
2183           v = VariantKriegspiel;
2184           break;
2185         case 17:
2186           v = VariantLosers;
2187           break;
2188         case 22:
2189           v = VariantFischeRandom;
2190           break;
2191         case 23:
2192           v = VariantCrazyhouse;
2193           break;
2194         case 24:
2195           v = VariantBughouse;
2196           break;
2197         case 25:
2198           v = Variant3Check;
2199           break;
2200         case 26:
2201           /* Not quite the same as FICS suicide! */
2202           v = VariantGiveaway;
2203           break;
2204         case 27:
2205           v = VariantAtomic;
2206           break;
2207         case 28:
2208           v = VariantShatranj;
2209           break;
2210
2211         /* Temporary names for future ICC types.  The name *will* change in
2212            the next xboard/WinBoard release after ICC defines it. */
2213         case 29:
2214           v = Variant29;
2215           break;
2216         case 30:
2217           v = Variant30;
2218           break;
2219         case 31:
2220           v = Variant31;
2221           break;
2222         case 32:
2223           v = Variant32;
2224           break;
2225         case 33:
2226           v = Variant33;
2227           break;
2228         case 34:
2229           v = Variant34;
2230           break;
2231         case 35:
2232           v = Variant35;
2233           break;
2234         case 36:
2235           v = Variant36;
2236           break;
2237         case 37:
2238           v = VariantShogi;
2239           break;
2240         case 38:
2241           v = VariantXiangqi;
2242           break;
2243         case 39:
2244           v = VariantCourier;
2245           break;
2246         case 40:
2247           v = VariantGothic;
2248           break;
2249         case 41:
2250           v = VariantCapablanca;
2251           break;
2252         case 42:
2253           v = VariantKnightmate;
2254           break;
2255         case 43:
2256           v = VariantFairy;
2257           break;
2258         case 44:
2259           v = VariantCylinder;
2260           break;
2261         case 45:
2262           v = VariantFalcon;
2263           break;
2264         case 46:
2265           v = VariantCapaRandom;
2266           break;
2267         case 47:
2268           v = VariantBerolina;
2269           break;
2270         case 48:
2271           v = VariantJanus;
2272           break;
2273         case 49:
2274           v = VariantSuper;
2275           break;
2276         case 50:
2277           v = VariantGreat;
2278           break;
2279         case -1:
2280           /* Found "wild" or "w" in the string but no number;
2281              must assume it's normal chess. */
2282           v = VariantNormal;
2283           break;
2284         default:
2285           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2286           if( (len >= MSG_SIZ) && appData.debugMode )
2287             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2288
2289           DisplayError(buf, 0);
2290           v = VariantUnknown;
2291           break;
2292         }
2293       }
2294     }
2295     if (appData.debugMode) {
2296       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2297               e, wnum, VariantName(v));
2298     }
2299     return v;
2300 }
2301
2302 static int leftover_start = 0, leftover_len = 0;
2303 char star_match[STAR_MATCH_N][MSG_SIZ];
2304
2305 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2306    advance *index beyond it, and set leftover_start to the new value of
2307    *index; else return FALSE.  If pattern contains the character '*', it
2308    matches any sequence of characters not containing '\r', '\n', or the
2309    character following the '*' (if any), and the matched sequence(s) are
2310    copied into star_match.
2311    */
2312 int
2313 looking_at ( char *buf, int *index, char *pattern)
2314 {
2315     char *bufp = &buf[*index], *patternp = pattern;
2316     int star_count = 0;
2317     char *matchp = star_match[0];
2318
2319     for (;;) {
2320         if (*patternp == NULLCHAR) {
2321             *index = leftover_start = bufp - buf;
2322             *matchp = NULLCHAR;
2323             return TRUE;
2324         }
2325         if (*bufp == NULLCHAR) return FALSE;
2326         if (*patternp == '*') {
2327             if (*bufp == *(patternp + 1)) {
2328                 *matchp = NULLCHAR;
2329                 matchp = star_match[++star_count];
2330                 patternp += 2;
2331                 bufp++;
2332                 continue;
2333             } else if (*bufp == '\n' || *bufp == '\r') {
2334                 patternp++;
2335                 if (*patternp == NULLCHAR)
2336                   continue;
2337                 else
2338                   return FALSE;
2339             } else {
2340                 *matchp++ = *bufp++;
2341                 continue;
2342             }
2343         }
2344         if (*patternp != *bufp) return FALSE;
2345         patternp++;
2346         bufp++;
2347     }
2348 }
2349
2350 void
2351 SendToPlayer (char *data, int length)
2352 {
2353     int error, outCount;
2354     outCount = OutputToProcess(NoProc, data, length, &error);
2355     if (outCount < length) {
2356         DisplayFatalError(_("Error writing to display"), error, 1);
2357     }
2358 }
2359
2360 void
2361 PackHolding (char packed[], char *holding)
2362 {
2363     char *p = holding;
2364     char *q = packed;
2365     int runlength = 0;
2366     int curr = 9999;
2367     do {
2368         if (*p == curr) {
2369             runlength++;
2370         } else {
2371             switch (runlength) {
2372               case 0:
2373                 break;
2374               case 1:
2375                 *q++ = curr;
2376                 break;
2377               case 2:
2378                 *q++ = curr;
2379                 *q++ = curr;
2380                 break;
2381               default:
2382                 sprintf(q, "%d", runlength);
2383                 while (*q) q++;
2384                 *q++ = curr;
2385                 break;
2386             }
2387             runlength = 1;
2388             curr = *p;
2389         }
2390     } while (*p++);
2391     *q = NULLCHAR;
2392 }
2393
2394 /* Telnet protocol requests from the front end */
2395 void
2396 TelnetRequest (unsigned char ddww, unsigned char option)
2397 {
2398     unsigned char msg[3];
2399     int outCount, outError;
2400
2401     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2402
2403     if (appData.debugMode) {
2404         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2405         switch (ddww) {
2406           case TN_DO:
2407             ddwwStr = "DO";
2408             break;
2409           case TN_DONT:
2410             ddwwStr = "DONT";
2411             break;
2412           case TN_WILL:
2413             ddwwStr = "WILL";
2414             break;
2415           case TN_WONT:
2416             ddwwStr = "WONT";
2417             break;
2418           default:
2419             ddwwStr = buf1;
2420             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2421             break;
2422         }
2423         switch (option) {
2424           case TN_ECHO:
2425             optionStr = "ECHO";
2426             break;
2427           default:
2428             optionStr = buf2;
2429             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2430             break;
2431         }
2432         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2433     }
2434     msg[0] = TN_IAC;
2435     msg[1] = ddww;
2436     msg[2] = option;
2437     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2438     if (outCount < 3) {
2439         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2440     }
2441 }
2442
2443 void
2444 DoEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DO, TN_ECHO);
2448 }
2449
2450 void
2451 DontEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DONT, TN_ECHO);
2455 }
2456
2457 void
2458 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2459 {
2460     /* put the holdings sent to us by the server on the board holdings area */
2461     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2462     char p;
2463     ChessSquare piece;
2464
2465     if(gameInfo.holdingsWidth < 2)  return;
2466     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2467         return; // prevent overwriting by pre-board holdings
2468
2469     if( (int)lowestPiece >= BlackPawn ) {
2470         holdingsColumn = 0;
2471         countsColumn = 1;
2472         holdingsStartRow = BOARD_HEIGHT-1;
2473         direction = -1;
2474     } else {
2475         holdingsColumn = BOARD_WIDTH-1;
2476         countsColumn = BOARD_WIDTH-2;
2477         holdingsStartRow = 0;
2478         direction = 1;
2479     }
2480
2481     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2482         board[i][holdingsColumn] = EmptySquare;
2483         board[i][countsColumn]   = (ChessSquare) 0;
2484     }
2485     while( (p=*holdings++) != NULLCHAR ) {
2486         piece = CharToPiece( ToUpper(p) );
2487         if(piece == EmptySquare) continue;
2488         /*j = (int) piece - (int) WhitePawn;*/
2489         j = PieceToNumber(piece);
2490         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2491         if(j < 0) continue;               /* should not happen */
2492         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2493         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2494         board[holdingsStartRow+j*direction][countsColumn]++;
2495     }
2496 }
2497
2498
2499 void
2500 VariantSwitch (Board board, VariantClass newVariant)
2501 {
2502    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2503    static Board oldBoard;
2504
2505    startedFromPositionFile = FALSE;
2506    if(gameInfo.variant == newVariant) return;
2507
2508    /* [HGM] This routine is called each time an assignment is made to
2509     * gameInfo.variant during a game, to make sure the board sizes
2510     * are set to match the new variant. If that means adding or deleting
2511     * holdings, we shift the playing board accordingly
2512     * This kludge is needed because in ICS observe mode, we get boards
2513     * of an ongoing game without knowing the variant, and learn about the
2514     * latter only later. This can be because of the move list we requested,
2515     * in which case the game history is refilled from the beginning anyway,
2516     * but also when receiving holdings of a crazyhouse game. In the latter
2517     * case we want to add those holdings to the already received position.
2518     */
2519
2520
2521    if (appData.debugMode) {
2522      fprintf(debugFP, "Switch board from %s to %s\n",
2523              VariantName(gameInfo.variant), VariantName(newVariant));
2524      setbuf(debugFP, NULL);
2525    }
2526    shuffleOpenings = 0;       /* [HGM] shuffle */
2527    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2528    switch(newVariant)
2529      {
2530      case VariantShogi:
2531        newWidth = 9;  newHeight = 9;
2532        gameInfo.holdingsSize = 7;
2533      case VariantBughouse:
2534      case VariantCrazyhouse:
2535        newHoldingsWidth = 2; break;
2536      case VariantGreat:
2537        newWidth = 10;
2538      case VariantSuper:
2539        newHoldingsWidth = 2;
2540        gameInfo.holdingsSize = 8;
2541        break;
2542      case VariantGothic:
2543      case VariantCapablanca:
2544      case VariantCapaRandom:
2545        newWidth = 10;
2546      default:
2547        newHoldingsWidth = gameInfo.holdingsSize = 0;
2548      };
2549
2550    if(newWidth  != gameInfo.boardWidth  ||
2551       newHeight != gameInfo.boardHeight ||
2552       newHoldingsWidth != gameInfo.holdingsWidth ) {
2553
2554      /* shift position to new playing area, if needed */
2555      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2556        for(i=0; i<BOARD_HEIGHT; i++)
2557          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2558            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2559              board[i][j];
2560        for(i=0; i<newHeight; i++) {
2561          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2562          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2563        }
2564      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2565        for(i=0; i<BOARD_HEIGHT; i++)
2566          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2567            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2568              board[i][j];
2569      }
2570      board[HOLDINGS_SET] = 0;
2571      gameInfo.boardWidth  = newWidth;
2572      gameInfo.boardHeight = newHeight;
2573      gameInfo.holdingsWidth = newHoldingsWidth;
2574      gameInfo.variant = newVariant;
2575      InitDrawingSizes(-2, 0);
2576    } else gameInfo.variant = newVariant;
2577    CopyBoard(oldBoard, board);   // remember correctly formatted board
2578      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2579    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2580 }
2581
2582 static int loggedOn = FALSE;
2583
2584 /*-- Game start info cache: --*/
2585 int gs_gamenum;
2586 char gs_kind[MSG_SIZ];
2587 static char player1Name[128] = "";
2588 static char player2Name[128] = "";
2589 static char cont_seq[] = "\n\\   ";
2590 static int player1Rating = -1;
2591 static int player2Rating = -1;
2592 /*----------------------------*/
2593
2594 ColorClass curColor = ColorNormal;
2595 int suppressKibitz = 0;
2596
2597 // [HGM] seekgraph
2598 Boolean soughtPending = FALSE;
2599 Boolean seekGraphUp;
2600 #define MAX_SEEK_ADS 200
2601 #define SQUARE 0x80
2602 char *seekAdList[MAX_SEEK_ADS];
2603 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2604 float tcList[MAX_SEEK_ADS];
2605 char colorList[MAX_SEEK_ADS];
2606 int nrOfSeekAds = 0;
2607 int minRating = 1010, maxRating = 2800;
2608 int hMargin = 10, vMargin = 20, h, w;
2609 extern int squareSize, lineGap;
2610
2611 void
2612 PlotSeekAd (int i)
2613 {
2614         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2615         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2616         if(r < minRating+100 && r >=0 ) r = minRating+100;
2617         if(r > maxRating) r = maxRating;
2618         if(tc < 1.f) tc = 1.f;
2619         if(tc > 95.f) tc = 95.f;
2620         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2621         y = ((double)r - minRating)/(maxRating - minRating)
2622             * (h-vMargin-squareSize/8-1) + vMargin;
2623         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2624         if(strstr(seekAdList[i], " u ")) color = 1;
2625         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2626            !strstr(seekAdList[i], "bullet") &&
2627            !strstr(seekAdList[i], "blitz") &&
2628            !strstr(seekAdList[i], "standard") ) color = 2;
2629         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2630         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2631 }
2632
2633 void
2634 PlotSingleSeekAd (int i)
2635 {
2636         PlotSeekAd(i);
2637 }
2638
2639 void
2640 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2641 {
2642         char buf[MSG_SIZ], *ext = "";
2643         VariantClass v = StringToVariant(type);
2644         if(strstr(type, "wild")) {
2645             ext = type + 4; // append wild number
2646             if(v == VariantFischeRandom) type = "chess960"; else
2647             if(v == VariantLoadable) type = "setup"; else
2648             type = VariantName(v);
2649         }
2650         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2651         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2652             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2653             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2654             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2655             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2656             seekNrList[nrOfSeekAds] = nr;
2657             zList[nrOfSeekAds] = 0;
2658             seekAdList[nrOfSeekAds++] = StrSave(buf);
2659             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2660         }
2661 }
2662
2663 void
2664 EraseSeekDot (int i)
2665 {
2666     int x = xList[i], y = yList[i], d=squareSize/4, k;
2667     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2668     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2669     // now replot every dot that overlapped
2670     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2671         int xx = xList[k], yy = yList[k];
2672         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2673             DrawSeekDot(xx, yy, colorList[k]);
2674     }
2675 }
2676
2677 void
2678 RemoveSeekAd (int nr)
2679 {
2680         int i;
2681         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2682             EraseSeekDot(i);
2683             if(seekAdList[i]) free(seekAdList[i]);
2684             seekAdList[i] = seekAdList[--nrOfSeekAds];
2685             seekNrList[i] = seekNrList[nrOfSeekAds];
2686             ratingList[i] = ratingList[nrOfSeekAds];
2687             colorList[i]  = colorList[nrOfSeekAds];
2688             tcList[i] = tcList[nrOfSeekAds];
2689             xList[i]  = xList[nrOfSeekAds];
2690             yList[i]  = yList[nrOfSeekAds];
2691             zList[i]  = zList[nrOfSeekAds];
2692             seekAdList[nrOfSeekAds] = NULL;
2693             break;
2694         }
2695 }
2696
2697 Boolean
2698 MatchSoughtLine (char *line)
2699 {
2700     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2701     int nr, base, inc, u=0; char dummy;
2702
2703     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2704        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2705        (u=1) &&
2706        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2707         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2708         // match: compact and save the line
2709         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2710         return TRUE;
2711     }
2712     return FALSE;
2713 }
2714
2715 int
2716 DrawSeekGraph ()
2717 {
2718     int i;
2719     if(!seekGraphUp) return FALSE;
2720     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2721     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2722
2723     DrawSeekBackground(0, 0, w, h);
2724     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2725     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2726     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2727         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2728         yy = h-1-yy;
2729         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2730         if(i%500 == 0) {
2731             char buf[MSG_SIZ];
2732             snprintf(buf, MSG_SIZ, "%d", i);
2733             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2734         }
2735     }
2736     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2737     for(i=1; i<100; i+=(i<10?1:5)) {
2738         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2739         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2740         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2741             char buf[MSG_SIZ];
2742             snprintf(buf, MSG_SIZ, "%d", i);
2743             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2744         }
2745     }
2746     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2747     return TRUE;
2748 }
2749
2750 int
2751 SeekGraphClick (ClickType click, int x, int y, int moving)
2752 {
2753     static int lastDown = 0, displayed = 0, lastSecond;
2754     if(y < 0) return FALSE;
2755     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2756         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2757         if(!seekGraphUp) return FALSE;
2758         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2759         DrawPosition(TRUE, NULL);
2760         return TRUE;
2761     }
2762     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2763         if(click == Release || moving) return FALSE;
2764         nrOfSeekAds = 0;
2765         soughtPending = TRUE;
2766         SendToICS(ics_prefix);
2767         SendToICS("sought\n"); // should this be "sought all"?
2768     } else { // issue challenge based on clicked ad
2769         int dist = 10000; int i, closest = 0, second = 0;
2770         for(i=0; i<nrOfSeekAds; i++) {
2771             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2772             if(d < dist) { dist = d; closest = i; }
2773             second += (d - zList[i] < 120); // count in-range ads
2774             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2775         }
2776         if(dist < 120) {
2777             char buf[MSG_SIZ];
2778             second = (second > 1);
2779             if(displayed != closest || second != lastSecond) {
2780                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2781                 lastSecond = second; displayed = closest;
2782             }
2783             if(click == Press) {
2784                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2785                 lastDown = closest;
2786                 return TRUE;
2787             } // on press 'hit', only show info
2788             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2789             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2790             SendToICS(ics_prefix);
2791             SendToICS(buf);
2792             return TRUE; // let incoming board of started game pop down the graph
2793         } else if(click == Release) { // release 'miss' is ignored
2794             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2795             if(moving == 2) { // right up-click
2796                 nrOfSeekAds = 0; // refresh graph
2797                 soughtPending = TRUE;
2798                 SendToICS(ics_prefix);
2799                 SendToICS("sought\n"); // should this be "sought all"?
2800             }
2801             return TRUE;
2802         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2803         // press miss or release hit 'pop down' seek graph
2804         seekGraphUp = FALSE;
2805         DrawPosition(TRUE, NULL);
2806     }
2807     return TRUE;
2808 }
2809
2810 void
2811 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2812 {
2813 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2814 #define STARTED_NONE 0
2815 #define STARTED_MOVES 1
2816 #define STARTED_BOARD 2
2817 #define STARTED_OBSERVE 3
2818 #define STARTED_HOLDINGS 4
2819 #define STARTED_CHATTER 5
2820 #define STARTED_COMMENT 6
2821 #define STARTED_MOVES_NOHIDE 7
2822
2823     static int started = STARTED_NONE;
2824     static char parse[20000];
2825     static int parse_pos = 0;
2826     static char buf[BUF_SIZE + 1];
2827     static int firstTime = TRUE, intfSet = FALSE;
2828     static ColorClass prevColor = ColorNormal;
2829     static int savingComment = FALSE;
2830     static int cmatch = 0; // continuation sequence match
2831     char *bp;
2832     char str[MSG_SIZ];
2833     int i, oldi;
2834     int buf_len;
2835     int next_out;
2836     int tkind;
2837     int backup;    /* [DM] For zippy color lines */
2838     char *p;
2839     char talker[MSG_SIZ]; // [HGM] chat
2840     int channel, collective=0;
2841
2842     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2843
2844     if (appData.debugMode) {
2845       if (!error) {
2846         fprintf(debugFP, "<ICS: ");
2847         show_bytes(debugFP, data, count);
2848         fprintf(debugFP, "\n");
2849       }
2850     }
2851
2852     if (appData.debugMode) { int f = forwardMostMove;
2853         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2856     }
2857     if (count > 0) {
2858         /* If last read ended with a partial line that we couldn't parse,
2859            prepend it to the new read and try again. */
2860         if (leftover_len > 0) {
2861             for (i=0; i<leftover_len; i++)
2862               buf[i] = buf[leftover_start + i];
2863         }
2864
2865     /* copy new characters into the buffer */
2866     bp = buf + leftover_len;
2867     buf_len=leftover_len;
2868     for (i=0; i<count; i++)
2869     {
2870         // ignore these
2871         if (data[i] == '\r')
2872             continue;
2873
2874         // join lines split by ICS?
2875         if (!appData.noJoin)
2876         {
2877             /*
2878                 Joining just consists of finding matches against the
2879                 continuation sequence, and discarding that sequence
2880                 if found instead of copying it.  So, until a match
2881                 fails, there's nothing to do since it might be the
2882                 complete sequence, and thus, something we don't want
2883                 copied.
2884             */
2885             if (data[i] == cont_seq[cmatch])
2886             {
2887                 cmatch++;
2888                 if (cmatch == strlen(cont_seq))
2889                 {
2890                     cmatch = 0; // complete match.  just reset the counter
2891
2892                     /*
2893                         it's possible for the ICS to not include the space
2894                         at the end of the last word, making our [correct]
2895                         join operation fuse two separate words.  the server
2896                         does this when the space occurs at the width setting.
2897                     */
2898                     if (!buf_len || buf[buf_len-1] != ' ')
2899                     {
2900                         *bp++ = ' ';
2901                         buf_len++;
2902                     }
2903                 }
2904                 continue;
2905             }
2906             else if (cmatch)
2907             {
2908                 /*
2909                     match failed, so we have to copy what matched before
2910                     falling through and copying this character.  In reality,
2911                     this will only ever be just the newline character, but
2912                     it doesn't hurt to be precise.
2913                 */
2914                 strncpy(bp, cont_seq, cmatch);
2915                 bp += cmatch;
2916                 buf_len += cmatch;
2917                 cmatch = 0;
2918             }
2919         }
2920
2921         // copy this char
2922         *bp++ = data[i];
2923         buf_len++;
2924     }
2925
2926         buf[buf_len] = NULLCHAR;
2927 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2928         next_out = 0;
2929         leftover_start = 0;
2930
2931         i = 0;
2932         while (i < buf_len) {
2933             /* Deal with part of the TELNET option negotiation
2934                protocol.  We refuse to do anything beyond the
2935                defaults, except that we allow the WILL ECHO option,
2936                which ICS uses to turn off password echoing when we are
2937                directly connected to it.  We reject this option
2938                if localLineEditing mode is on (always on in xboard)
2939                and we are talking to port 23, which might be a real
2940                telnet server that will try to keep WILL ECHO on permanently.
2941              */
2942             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2943                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2944                 unsigned char option;
2945                 oldi = i;
2946                 switch ((unsigned char) buf[++i]) {
2947                   case TN_WILL:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<WILL ");
2950                     switch (option = (unsigned char) buf[++i]) {
2951                       case TN_ECHO:
2952                         if (appData.debugMode)
2953                           fprintf(debugFP, "ECHO ");
2954                         /* Reply only if this is a change, according
2955                            to the protocol rules. */
2956                         if (remoteEchoOption) break;
2957                         if (appData.localLineEditing &&
2958                             atoi(appData.icsPort) == TN_PORT) {
2959                             TelnetRequest(TN_DONT, TN_ECHO);
2960                         } else {
2961                             EchoOff();
2962                             TelnetRequest(TN_DO, TN_ECHO);
2963                             remoteEchoOption = TRUE;
2964                         }
2965                         break;
2966                       default:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "%d ", option);
2969                         /* Whatever this is, we don't want it. */
2970                         TelnetRequest(TN_DONT, option);
2971                         break;
2972                     }
2973                     break;
2974                   case TN_WONT:
2975                     if (appData.debugMode)
2976                       fprintf(debugFP, "\n<WONT ");
2977                     switch (option = (unsigned char) buf[++i]) {
2978                       case TN_ECHO:
2979                         if (appData.debugMode)
2980                           fprintf(debugFP, "ECHO ");
2981                         /* Reply only if this is a change, according
2982                            to the protocol rules. */
2983                         if (!remoteEchoOption) break;
2984                         EchoOn();
2985                         TelnetRequest(TN_DONT, TN_ECHO);
2986                         remoteEchoOption = FALSE;
2987                         break;
2988                       default:
2989                         if (appData.debugMode)
2990                           fprintf(debugFP, "%d ", (unsigned char) option);
2991                         /* Whatever this is, it must already be turned
2992                            off, because we never agree to turn on
2993                            anything non-default, so according to the
2994                            protocol rules, we don't reply. */
2995                         break;
2996                     }
2997                     break;
2998                   case TN_DO:
2999                     if (appData.debugMode)
3000                       fprintf(debugFP, "\n<DO ");
3001                     switch (option = (unsigned char) buf[++i]) {
3002                       default:
3003                         /* Whatever this is, we refuse to do it. */
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", option);
3006                         TelnetRequest(TN_WONT, option);
3007                         break;
3008                     }
3009                     break;
3010                   case TN_DONT:
3011                     if (appData.debugMode)
3012                       fprintf(debugFP, "\n<DONT ");
3013                     switch (option = (unsigned char) buf[++i]) {
3014                       default:
3015                         if (appData.debugMode)
3016                           fprintf(debugFP, "%d ", option);
3017                         /* Whatever this is, we are already not doing
3018                            it, because we never agree to do anything
3019                            non-default, so according to the protocol
3020                            rules, we don't reply. */
3021                         break;
3022                     }
3023                     break;
3024                   case TN_IAC:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<IAC ");
3027                     /* Doubled IAC; pass it through */
3028                     i--;
3029                     break;
3030                   default:
3031                     if (appData.debugMode)
3032                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3033                     /* Drop all other telnet commands on the floor */
3034                     break;
3035                 }
3036                 if (oldi > next_out)
3037                   SendToPlayer(&buf[next_out], oldi - next_out);
3038                 if (++i > next_out)
3039                   next_out = i;
3040                 continue;
3041             }
3042
3043             /* OK, this at least will *usually* work */
3044             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3045                 loggedOn = TRUE;
3046             }
3047
3048             if (loggedOn && !intfSet) {
3049                 if (ics_type == ICS_ICC) {
3050                   snprintf(str, MSG_SIZ,
3051                           "/set-quietly interface %s\n/set-quietly style 12\n",
3052                           programVersion);
3053                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3055                 } else if (ics_type == ICS_CHESSNET) {
3056                   snprintf(str, MSG_SIZ, "/style 12\n");
3057                 } else {
3058                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3059                   strcat(str, programVersion);
3060                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3063 #ifdef WIN32
3064                   strcat(str, "$iset nohighlight 1\n");
3065 #endif
3066                   strcat(str, "$iset lock 1\n$style 12\n");
3067                 }
3068                 SendToICS(str);
3069                 NotifyFrontendLogin();
3070                 intfSet = TRUE;
3071             }
3072
3073             if (started == STARTED_COMMENT) {
3074                 /* Accumulate characters in comment */
3075                 parse[parse_pos++] = buf[i];
3076                 if (buf[i] == '\n') {
3077                     parse[parse_pos] = NULLCHAR;
3078                     if(chattingPartner>=0) {
3079                         char mess[MSG_SIZ];
3080                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3081                         OutputChatMessage(chattingPartner, mess);
3082                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3083                             int p;
3084                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3085                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3086                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3087                                 OutputChatMessage(p, mess);
3088                                 break;
3089                             }
3090                         }
3091                         chattingPartner = -1;
3092                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3093                         collective = 0;
3094                     } else
3095                     if(!suppressKibitz) // [HGM] kibitz
3096                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3097                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3098                         int nrDigit = 0, nrAlph = 0, j;
3099                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3100                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3101                         parse[parse_pos] = NULLCHAR;
3102                         // try to be smart: if it does not look like search info, it should go to
3103                         // ICS interaction window after all, not to engine-output window.
3104                         for(j=0; j<parse_pos; j++) { // count letters and digits
3105                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3106                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3107                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3108                         }
3109                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3110                             int depth=0; float score;
3111                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3112                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3113                                 pvInfoList[forwardMostMove-1].depth = depth;
3114                                 pvInfoList[forwardMostMove-1].score = 100*score;
3115                             }
3116                             OutputKibitz(suppressKibitz, parse);
3117                         } else {
3118                             char tmp[MSG_SIZ];
3119                             if(gameMode == IcsObserving) // restore original ICS messages
3120                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3121                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3122                             else
3123                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3124                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3125                             SendToPlayer(tmp, strlen(tmp));
3126                         }
3127                         next_out = i+1; // [HGM] suppress printing in ICS window
3128                     }
3129                     started = STARTED_NONE;
3130                 } else {
3131                     /* Don't match patterns against characters in comment */
3132                     i++;
3133                     continue;
3134                 }
3135             }
3136             if (started == STARTED_CHATTER) {
3137                 if (buf[i] != '\n') {
3138                     /* Don't match patterns against characters in chatter */
3139                     i++;
3140                     continue;
3141                 }
3142                 started = STARTED_NONE;
3143                 if(suppressKibitz) next_out = i+1;
3144             }
3145
3146             /* Kludge to deal with rcmd protocol */
3147             if (firstTime && looking_at(buf, &i, "\001*")) {
3148                 DisplayFatalError(&buf[1], 0, 1);
3149                 continue;
3150             } else {
3151                 firstTime = FALSE;
3152             }
3153
3154             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3155                 ics_type = ICS_ICC;
3156                 ics_prefix = "/";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3162                 ics_type = ICS_FICS;
3163                 ics_prefix = "$";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3169                 ics_type = ICS_CHESSNET;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175
3176             if (!loggedOn &&
3177                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3178                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3179                  looking_at(buf, &i, "will be \"*\""))) {
3180               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3181               continue;
3182             }
3183
3184             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3185               char buf[MSG_SIZ];
3186               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3187               DisplayIcsInteractionTitle(buf);
3188               have_set_title = TRUE;
3189             }
3190
3191             /* skip finger notes */
3192             if (started == STARTED_NONE &&
3193                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3194                  (buf[i] == '1' && buf[i+1] == '0')) &&
3195                 buf[i+2] == ':' && buf[i+3] == ' ') {
3196               started = STARTED_CHATTER;
3197               i += 3;
3198               continue;
3199             }
3200
3201             oldi = i;
3202             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3203             if(appData.seekGraph) {
3204                 if(soughtPending && MatchSoughtLine(buf+i)) {
3205                     i = strstr(buf+i, "rated") - buf;
3206                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3207                     next_out = leftover_start = i;
3208                     started = STARTED_CHATTER;
3209                     suppressKibitz = TRUE;
3210                     continue;
3211                 }
3212                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3213                         && looking_at(buf, &i, "* ads displayed")) {
3214                     soughtPending = FALSE;
3215                     seekGraphUp = TRUE;
3216                     DrawSeekGraph();
3217                     continue;
3218                 }
3219                 if(appData.autoRefresh) {
3220                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3221                         int s = (ics_type == ICS_ICC); // ICC format differs
3222                         if(seekGraphUp)
3223                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3224                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3225                         looking_at(buf, &i, "*% "); // eat prompt
3226                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3227                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228                         next_out = i; // suppress
3229                         continue;
3230                     }
3231                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3232                         char *p = star_match[0];
3233                         while(*p) {
3234                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3235                             while(*p && *p++ != ' '); // next
3236                         }
3237                         looking_at(buf, &i, "*% "); // eat prompt
3238                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = i;
3240                         continue;
3241                     }
3242                 }
3243             }
3244
3245             /* skip formula vars */
3246             if (started == STARTED_NONE &&
3247                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3248               started = STARTED_CHATTER;
3249               i += 3;
3250               continue;
3251             }
3252
3253             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3254             if (appData.autoKibitz && started == STARTED_NONE &&
3255                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3256                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3257                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3258                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3259                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3260                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3261                         suppressKibitz = TRUE;
3262                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263                         next_out = i;
3264                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3265                                 && (gameMode == IcsPlayingWhite)) ||
3266                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3267                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3268                             started = STARTED_CHATTER; // own kibitz we simply discard
3269                         else {
3270                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3271                             parse_pos = 0; parse[0] = NULLCHAR;
3272                             savingComment = TRUE;
3273                             suppressKibitz = gameMode != IcsObserving ? 2 :
3274                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3275                         }
3276                         continue;
3277                 } else
3278                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3279                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3280                          && atoi(star_match[0])) {
3281                     // suppress the acknowledgements of our own autoKibitz
3282                     char *p;
3283                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3284                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3285                     SendToPlayer(star_match[0], strlen(star_match[0]));
3286                     if(looking_at(buf, &i, "*% ")) // eat prompt
3287                         suppressKibitz = FALSE;
3288                     next_out = i;
3289                     continue;
3290                 }
3291             } // [HGM] kibitz: end of patch
3292
3293             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3294
3295             // [HGM] chat: intercept tells by users for which we have an open chat window
3296             channel = -1;
3297             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3298                                            looking_at(buf, &i, "* whispers:") ||
3299                                            looking_at(buf, &i, "* kibitzes:") ||
3300                                            looking_at(buf, &i, "* shouts:") ||
3301                                            looking_at(buf, &i, "* c-shouts:") ||
3302                                            looking_at(buf, &i, "--> * ") ||
3303                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3306                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3307                 int p;
3308                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3309                 chattingPartner = -1; collective = 0;
3310
3311                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3312                 for(p=0; p<MAX_CHAT; p++) {
3313                     collective = 1;
3314                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3315                     talker[0] = '['; strcat(talker, "] ");
3316                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3317                     chattingPartner = p; break;
3318                     }
3319                 } else
3320                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3321                 for(p=0; p<MAX_CHAT; p++) {
3322                     collective = 1;
3323                     if(!strcmp("kibitzes", chatPartner[p])) {
3324                         talker[0] = '['; strcat(talker, "] ");
3325                         chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("whispers", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3337                   if(buf[i-8] == '-' && buf[i-3] == 't')
3338                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3339                     collective = 1;
3340                     if(!strcmp("c-shouts", chatPartner[p])) {
3341                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3342                         chattingPartner = p; break;
3343                     }
3344                   }
3345                   if(chattingPartner < 0)
3346                   for(p=0; p<MAX_CHAT; p++) {
3347                     collective = 1;
3348                     if(!strcmp("shouts", chatPartner[p])) {
3349                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3350                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3351                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3352                         chattingPartner = p; break;
3353                     }
3354                   }
3355                 }
3356                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3357                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3358                     talker[0] = 0;
3359                     Colorize(ColorTell, FALSE);
3360                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3361                     collective |= 2;
3362                     chattingPartner = p; break;
3363                 }
3364                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3365                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3366                     started = STARTED_COMMENT;
3367                     parse_pos = 0; parse[0] = NULLCHAR;
3368                     savingComment = 3 + chattingPartner; // counts as TRUE
3369                     if(collective == 3) i = oldi; else {
3370                         suppressKibitz = TRUE;
3371                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3372                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3373                         continue;
3374                     }
3375                 }
3376             } // [HGM] chat: end of patch
3377
3378           backup = i;
3379             if (appData.zippyTalk || appData.zippyPlay) {
3380                 /* [DM] Backup address for color zippy lines */
3381 #if ZIPPY
3382                if (loggedOn == TRUE)
3383                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3384                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3385 #endif
3386             } // [DM] 'else { ' deleted
3387                 if (
3388                     /* Regular tells and says */
3389                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3390                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3391                     looking_at(buf, &i, "* says: ") ||
3392                     /* Don't color "message" or "messages" output */
3393                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3394                     looking_at(buf, &i, "*. * at *:*: ") ||
3395                     looking_at(buf, &i, "--* (*:*): ") ||
3396                     /* Message notifications (same color as tells) */
3397                     looking_at(buf, &i, "* has left a message ") ||
3398                     looking_at(buf, &i, "* just sent you a message:\n") ||
3399                     /* Whispers and kibitzes */
3400                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3401                     looking_at(buf, &i, "* kibitzes: ") ||
3402                     /* Channel tells */
3403                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3404
3405                   if (tkind == 1 && strchr(star_match[0], ':')) {
3406                       /* Avoid "tells you:" spoofs in channels */
3407                      tkind = 3;
3408                   }
3409                   if (star_match[0][0] == NULLCHAR ||
3410                       strchr(star_match[0], ' ') ||
3411                       (tkind == 3 && strchr(star_match[1], ' '))) {
3412                     /* Reject bogus matches */
3413                     i = oldi;
3414                   } else {
3415                     if (appData.colorize) {
3416                       if (oldi > next_out) {
3417                         SendToPlayer(&buf[next_out], oldi - next_out);
3418                         next_out = oldi;
3419                       }
3420                       switch (tkind) {
3421                       case 1:
3422                         Colorize(ColorTell, FALSE);
3423                         curColor = ColorTell;
3424                         break;
3425                       case 2:
3426                         Colorize(ColorKibitz, FALSE);
3427                         curColor = ColorKibitz;
3428                         break;
3429                       case 3:
3430                         p = strrchr(star_match[1], '(');
3431                         if (p == NULL) {
3432                           p = star_match[1];
3433                         } else {
3434                           p++;
3435                         }
3436                         if (atoi(p) == 1) {
3437                           Colorize(ColorChannel1, FALSE);
3438                           curColor = ColorChannel1;
3439                         } else {
3440                           Colorize(ColorChannel, FALSE);
3441                           curColor = ColorChannel;
3442                         }
3443                         break;
3444                       case 5:
3445                         curColor = ColorNormal;
3446                         break;
3447                       }
3448                     }
3449                     if (started == STARTED_NONE && appData.autoComment &&
3450                         (gameMode == IcsObserving ||
3451                          gameMode == IcsPlayingWhite ||
3452                          gameMode == IcsPlayingBlack)) {
3453                       parse_pos = i - oldi;
3454                       memcpy(parse, &buf[oldi], parse_pos);
3455                       parse[parse_pos] = NULLCHAR;
3456                       started = STARTED_COMMENT;
3457                       savingComment = TRUE;
3458                     } else if(collective != 3) {
3459                       started = STARTED_CHATTER;
3460                       savingComment = FALSE;
3461                     }
3462                     loggedOn = TRUE;
3463                     continue;
3464                   }
3465                 }
3466
3467                 if (looking_at(buf, &i, "* s-shouts: ") ||
3468                     looking_at(buf, &i, "* c-shouts: ")) {
3469                     if (appData.colorize) {
3470                         if (oldi > next_out) {
3471                             SendToPlayer(&buf[next_out], oldi - next_out);
3472                             next_out = oldi;
3473                         }
3474                         Colorize(ColorSShout, FALSE);
3475                         curColor = ColorSShout;
3476                     }
3477                     loggedOn = TRUE;
3478                     started = STARTED_CHATTER;
3479                     continue;
3480                 }
3481
3482                 if (looking_at(buf, &i, "--->")) {
3483                     loggedOn = TRUE;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "* shouts: ") ||
3488                     looking_at(buf, &i, "--> ")) {
3489                     if (appData.colorize) {
3490                         if (oldi > next_out) {
3491                             SendToPlayer(&buf[next_out], oldi - next_out);
3492                             next_out = oldi;
3493                         }
3494                         Colorize(ColorShout, FALSE);
3495                         curColor = ColorShout;
3496                     }
3497                     loggedOn = TRUE;
3498                     started = STARTED_CHATTER;
3499                     continue;
3500                 }
3501
3502                 if (looking_at( buf, &i, "Challenge:")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorChallenge, FALSE);
3509                         curColor = ColorChallenge;
3510                     }
3511                     loggedOn = TRUE;
3512                     continue;
3513                 }
3514
3515                 if (looking_at(buf, &i, "* offers you") ||
3516                     looking_at(buf, &i, "* offers to be") ||
3517                     looking_at(buf, &i, "* would like to") ||
3518                     looking_at(buf, &i, "* requests to") ||
3519                     looking_at(buf, &i, "Your opponent offers") ||
3520                     looking_at(buf, &i, "Your opponent requests")) {
3521
3522                     if (appData.colorize) {
3523                         if (oldi > next_out) {
3524                             SendToPlayer(&buf[next_out], oldi - next_out);
3525                             next_out = oldi;
3526                         }
3527                         Colorize(ColorRequest, FALSE);
3528                         curColor = ColorRequest;
3529                     }
3530                     continue;
3531                 }
3532
3533                 if (looking_at(buf, &i, "* (*) seeking")) {
3534                     if (appData.colorize) {
3535                         if (oldi > next_out) {
3536                             SendToPlayer(&buf[next_out], oldi - next_out);
3537                             next_out = oldi;
3538                         }
3539                         Colorize(ColorSeek, FALSE);
3540                         curColor = ColorSeek;
3541                     }
3542                     continue;
3543             }
3544
3545           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3546
3547             if (looking_at(buf, &i, "\\   ")) {
3548                 if (prevColor != ColorNormal) {
3549                     if (oldi > next_out) {
3550                         SendToPlayer(&buf[next_out], oldi - next_out);
3551                         next_out = oldi;
3552                     }
3553                     Colorize(prevColor, TRUE);
3554                     curColor = prevColor;
3555                 }
3556                 if (savingComment) {
3557                     parse_pos = i - oldi;
3558                     memcpy(parse, &buf[oldi], parse_pos);
3559                     parse[parse_pos] = NULLCHAR;
3560                     started = STARTED_COMMENT;
3561                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3562                         chattingPartner = savingComment - 3; // kludge to remember the box
3563                 } else {
3564                     started = STARTED_CHATTER;
3565                 }
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "Black Strength :") ||
3570                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3571                 looking_at(buf, &i, "<10>") ||
3572                 looking_at(buf, &i, "#@#")) {
3573                 /* Wrong board style */
3574                 loggedOn = TRUE;
3575                 SendToICS(ics_prefix);
3576                 SendToICS("set style 12\n");
3577                 SendToICS(ics_prefix);
3578                 SendToICS("refresh\n");
3579                 continue;
3580             }
3581
3582             if (looking_at(buf, &i, "login:")) {
3583               if (!have_sent_ICS_logon) {
3584                 if(ICSInitScript())
3585                   have_sent_ICS_logon = 1;
3586                 else // no init script was found
3587                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3588               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3589                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3590               }
3591                 continue;
3592             }
3593
3594             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3595                 (looking_at(buf, &i, "\n<12> ") ||
3596                  looking_at(buf, &i, "<12> "))) {
3597                 loggedOn = TRUE;
3598                 if (oldi > next_out) {
3599                     SendToPlayer(&buf[next_out], oldi - next_out);
3600                 }
3601                 next_out = i;
3602                 started = STARTED_BOARD;
3603                 parse_pos = 0;
3604                 continue;
3605             }
3606
3607             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3608                 looking_at(buf, &i, "<b1> ")) {
3609                 if (oldi > next_out) {
3610                     SendToPlayer(&buf[next_out], oldi - next_out);
3611                 }
3612                 next_out = i;
3613                 started = STARTED_HOLDINGS;
3614                 parse_pos = 0;
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3619                 loggedOn = TRUE;
3620                 /* Header for a move list -- first line */
3621
3622                 switch (ics_getting_history) {
3623                   case H_FALSE:
3624                     switch (gameMode) {
3625                       case IcsIdle:
3626                       case BeginningOfGame:
3627                         /* User typed "moves" or "oldmoves" while we
3628                            were idle.  Pretend we asked for these
3629                            moves and soak them up so user can step
3630                            through them and/or save them.
3631                            */
3632                         Reset(FALSE, TRUE);
3633                         gameMode = IcsObserving;
3634                         ModeHighlight();
3635                         ics_gamenum = -1;
3636                         ics_getting_history = H_GOT_UNREQ_HEADER;
3637                         break;
3638                       case EditGame: /*?*/
3639                       case EditPosition: /*?*/
3640                         /* Should above feature work in these modes too? */
3641                         /* For now it doesn't */
3642                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3643                         break;
3644                       default:
3645                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3646                         break;
3647                     }
3648                     break;
3649                   case H_REQUESTED:
3650                     /* Is this the right one? */
3651                     if (gameInfo.white && gameInfo.black &&
3652                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3653                         strcmp(gameInfo.black, star_match[2]) == 0) {
3654                         /* All is well */
3655                         ics_getting_history = H_GOT_REQ_HEADER;
3656                     }
3657                     break;
3658                   case H_GOT_REQ_HEADER:
3659                   case H_GOT_UNREQ_HEADER:
3660                   case H_GOT_UNWANTED_HEADER:
3661                   case H_GETTING_MOVES:
3662                     /* Should not happen */
3663                     DisplayError(_("Error gathering move list: two headers"), 0);
3664                     ics_getting_history = H_FALSE;
3665                     break;
3666                 }
3667
3668                 /* Save player ratings into gameInfo if needed */
3669                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3670                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3671                     (gameInfo.whiteRating == -1 ||
3672                      gameInfo.blackRating == -1)) {
3673
3674                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3675                     gameInfo.blackRating = string_to_rating(star_match[3]);
3676                     if (appData.debugMode)
3677                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3678                               gameInfo.whiteRating, gameInfo.blackRating);
3679                 }
3680                 continue;
3681             }
3682
3683             if (looking_at(buf, &i,
3684               "* * match, initial time: * minute*, increment: * second")) {
3685                 /* Header for a move list -- second line */
3686                 /* Initial board will follow if this is a wild game */
3687                 if (gameInfo.event != NULL) free(gameInfo.event);
3688                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3689                 gameInfo.event = StrSave(str);
3690                 /* [HGM] we switched variant. Translate boards if needed. */
3691                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3692                 continue;
3693             }
3694
3695             if (looking_at(buf, &i, "Move  ")) {
3696                 /* Beginning of a move list */
3697                 switch (ics_getting_history) {
3698                   case H_FALSE:
3699                     /* Normally should not happen */
3700                     /* Maybe user hit reset while we were parsing */
3701                     break;
3702                   case H_REQUESTED:
3703                     /* Happens if we are ignoring a move list that is not
3704                      * the one we just requested.  Common if the user
3705                      * tries to observe two games without turning off
3706                      * getMoveList */
3707                     break;
3708                   case H_GETTING_MOVES:
3709                     /* Should not happen */
3710                     DisplayError(_("Error gathering move list: nested"), 0);
3711                     ics_getting_history = H_FALSE;
3712                     break;
3713                   case H_GOT_REQ_HEADER:
3714                     ics_getting_history = H_GETTING_MOVES;
3715                     started = STARTED_MOVES;
3716                     parse_pos = 0;
3717                     if (oldi > next_out) {
3718                         SendToPlayer(&buf[next_out], oldi - next_out);
3719                     }
3720                     break;
3721                   case H_GOT_UNREQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES_NOHIDE;
3724                     parse_pos = 0;
3725                     break;
3726                   case H_GOT_UNWANTED_HEADER:
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                 }
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "% ") ||
3734                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3735                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3736                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3737                     soughtPending = FALSE;
3738                     seekGraphUp = TRUE;
3739                     DrawSeekGraph();
3740                 }
3741                 if(suppressKibitz) next_out = i;
3742                 savingComment = FALSE;
3743                 suppressKibitz = 0;
3744                 switch (started) {
3745                   case STARTED_MOVES:
3746                   case STARTED_MOVES_NOHIDE:
3747                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3748                     parse[parse_pos + i - oldi] = NULLCHAR;
3749                     ParseGameHistory(parse);
3750 #if ZIPPY
3751                     if (appData.zippyPlay && first.initDone) {
3752                         FeedMovesToProgram(&first, forwardMostMove);
3753                         if (gameMode == IcsPlayingWhite) {
3754                             if (WhiteOnMove(forwardMostMove)) {
3755                                 if (first.sendTime) {
3756                                   if (first.useColors) {
3757                                     SendToProgram("black\n", &first);
3758                                   }
3759                                   SendTimeRemaining(&first, TRUE);
3760                                 }
3761                                 if (first.useColors) {
3762                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3763                                 }
3764                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3765                                 first.maybeThinking = TRUE;
3766                             } else {
3767                                 if (first.usePlayother) {
3768                                   if (first.sendTime) {
3769                                     SendTimeRemaining(&first, TRUE);
3770                                   }
3771                                   SendToProgram("playother\n", &first);
3772                                   firstMove = FALSE;
3773                                 } else {
3774                                   firstMove = TRUE;
3775                                 }
3776                             }
3777                         } else if (gameMode == IcsPlayingBlack) {
3778                             if (!WhiteOnMove(forwardMostMove)) {
3779                                 if (first.sendTime) {
3780                                   if (first.useColors) {
3781                                     SendToProgram("white\n", &first);
3782                                   }
3783                                   SendTimeRemaining(&first, FALSE);
3784                                 }
3785                                 if (first.useColors) {
3786                                   SendToProgram("black\n", &first);
3787                                 }
3788                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3789                                 first.maybeThinking = TRUE;
3790                             } else {
3791                                 if (first.usePlayother) {
3792                                   if (first.sendTime) {
3793                                     SendTimeRemaining(&first, FALSE);
3794                                   }
3795                                   SendToProgram("playother\n", &first);
3796                                   firstMove = FALSE;
3797                                 } else {
3798                                   firstMove = TRUE;
3799                                 }
3800                             }
3801                         }
3802                     }
3803 #endif
3804                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3805                         /* Moves came from oldmoves or moves command
3806                            while we weren't doing anything else.
3807                            */
3808                         currentMove = forwardMostMove;
3809                         ClearHighlights();/*!!could figure this out*/
3810                         flipView = appData.flipView;
3811                         DrawPosition(TRUE, boards[currentMove]);
3812                         DisplayBothClocks();
3813                         snprintf(str, MSG_SIZ, "%s %s %s",
3814                                 gameInfo.white, _("vs."),  gameInfo.black);
3815                         DisplayTitle(str);
3816                         gameMode = IcsIdle;
3817                     } else {
3818                         /* Moves were history of an active game */
3819                         if (gameInfo.resultDetails != NULL) {
3820                             free(gameInfo.resultDetails);
3821                             gameInfo.resultDetails = NULL;
3822                         }
3823                     }
3824                     HistorySet(parseList, backwardMostMove,
3825                                forwardMostMove, currentMove-1);
3826                     DisplayMove(currentMove - 1);
3827                     if (started == STARTED_MOVES) next_out = i;
3828                     started = STARTED_NONE;
3829                     ics_getting_history = H_FALSE;
3830                     break;
3831
3832                   case STARTED_OBSERVE:
3833                     started = STARTED_NONE;
3834                     SendToICS(ics_prefix);
3835                     SendToICS("refresh\n");
3836                     break;
3837
3838                   default:
3839                     break;
3840                 }
3841                 if(bookHit) { // [HGM] book: simulate book reply
3842                     static char bookMove[MSG_SIZ]; // a bit generous?
3843
3844                     programStats.nodes = programStats.depth = programStats.time =
3845                     programStats.score = programStats.got_only_move = 0;
3846                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3847
3848                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3849                     strcat(bookMove, bookHit);
3850                     HandleMachineMove(bookMove, &first);
3851                 }
3852                 continue;
3853             }
3854
3855             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3856                  started == STARTED_HOLDINGS ||
3857                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3858                 /* Accumulate characters in move list or board */
3859                 parse[parse_pos++] = buf[i];
3860             }
3861
3862             /* Start of game messages.  Mostly we detect start of game
3863                when the first board image arrives.  On some versions
3864                of the ICS, though, we need to do a "refresh" after starting
3865                to observe in order to get the current board right away. */
3866             if (looking_at(buf, &i, "Adding game * to observation list")) {
3867                 started = STARTED_OBSERVE;
3868                 continue;
3869             }
3870
3871             /* Handle auto-observe */
3872             if (appData.autoObserve &&
3873                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3874                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3875                 char *player;
3876                 /* Choose the player that was highlighted, if any. */
3877                 if (star_match[0][0] == '\033' ||
3878                     star_match[1][0] != '\033') {
3879                     player = star_match[0];
3880                 } else {
3881                     player = star_match[2];
3882                 }
3883                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3884                         ics_prefix, StripHighlightAndTitle(player));
3885                 SendToICS(str);
3886
3887                 /* Save ratings from notify string */
3888                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[3]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Deal with automatic examine mode after a game,
3903                and with IcsObserving -> IcsExamining transition */
3904             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3905                 looking_at(buf, &i, "has made you an examiner of game *")) {
3906
3907                 int gamenum = atoi(star_match[0]);
3908                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3909                     gamenum == ics_gamenum) {
3910                     /* We were already playing or observing this game;
3911                        no need to refetch history */
3912                     gameMode = IcsExamining;
3913                     if (pausing) {
3914                         pauseExamForwardMostMove = forwardMostMove;
3915                     } else if (currentMove < forwardMostMove) {
3916                         ForwardInner(forwardMostMove);
3917                     }
3918                 } else {
3919                     /* I don't think this case really can happen */
3920                     SendToICS(ics_prefix);
3921                     SendToICS("refresh\n");
3922                 }
3923                 continue;
3924             }
3925
3926             /* Error messages */
3927 //          if (ics_user_moved) {
3928             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3929                 if (looking_at(buf, &i, "Illegal move") ||
3930                     looking_at(buf, &i, "Not a legal move") ||
3931                     looking_at(buf, &i, "Your king is in check") ||
3932                     looking_at(buf, &i, "It isn't your turn") ||
3933                     looking_at(buf, &i, "It is not your move")) {
3934                     /* Illegal move */
3935                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3936                         currentMove = forwardMostMove-1;
3937                         DisplayMove(currentMove - 1); /* before DMError */
3938                         DrawPosition(FALSE, boards[currentMove]);
3939                         SwitchClocks(forwardMostMove-1); // [HGM] race
3940                         DisplayBothClocks();
3941                     }
3942                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3943                     ics_user_moved = 0;
3944                     continue;
3945                 }
3946             }
3947
3948             if (looking_at(buf, &i, "still have time") ||
3949                 looking_at(buf, &i, "not out of time") ||
3950                 looking_at(buf, &i, "either player is out of time") ||
3951                 looking_at(buf, &i, "has timeseal; checking")) {
3952                 /* We must have called his flag a little too soon */
3953                 whiteFlag = blackFlag = FALSE;
3954                 continue;
3955             }
3956
3957             if (looking_at(buf, &i, "added * seconds to") ||
3958                 looking_at(buf, &i, "seconds were added to")) {
3959                 /* Update the clocks */
3960                 SendToICS(ics_prefix);
3961                 SendToICS("refresh\n");
3962                 continue;
3963             }
3964
3965             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3966                 ics_clock_paused = TRUE;
3967                 StopClocks();
3968                 continue;
3969             }
3970
3971             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3972                 ics_clock_paused = FALSE;
3973                 StartClocks();
3974                 continue;
3975             }
3976
3977             /* Grab player ratings from the Creating: message.
3978                Note we have to check for the special case when
3979                the ICS inserts things like [white] or [black]. */
3980             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3981                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3982                 /* star_matches:
3983                    0    player 1 name (not necessarily white)
3984                    1    player 1 rating
3985                    2    empty, white, or black (IGNORED)
3986                    3    player 2 name (not necessarily black)
3987                    4    player 2 rating
3988
3989                    The names/ratings are sorted out when the game
3990                    actually starts (below).
3991                 */
3992                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3993                 player1Rating = string_to_rating(star_match[1]);
3994                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3995                 player2Rating = string_to_rating(star_match[4]);
3996
3997                 if (appData.debugMode)
3998                   fprintf(debugFP,
3999                           "Ratings from 'Creating:' %s %d, %s %d\n",
4000                           player1Name, player1Rating,
4001                           player2Name, player2Rating);
4002
4003                 continue;
4004             }
4005
4006             /* Improved generic start/end-of-game messages */
4007             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4008                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4009                 /* If tkind == 0: */
4010                 /* star_match[0] is the game number */
4011                 /*           [1] is the white player's name */
4012                 /*           [2] is the black player's name */
4013                 /* For end-of-game: */
4014                 /*           [3] is the reason for the game end */
4015                 /*           [4] is a PGN end game-token, preceded by " " */
4016                 /* For start-of-game: */
4017                 /*           [3] begins with "Creating" or "Continuing" */
4018                 /*           [4] is " *" or empty (don't care). */
4019                 int gamenum = atoi(star_match[0]);
4020                 char *whitename, *blackname, *why, *endtoken;
4021                 ChessMove endtype = EndOfFile;
4022
4023                 if (tkind == 0) {
4024                   whitename = star_match[1];
4025                   blackname = star_match[2];
4026                   why = star_match[3];
4027                   endtoken = star_match[4];
4028                 } else {
4029                   whitename = star_match[1];
4030                   blackname = star_match[3];
4031                   why = star_match[5];
4032                   endtoken = star_match[6];
4033                 }
4034
4035                 /* Game start messages */
4036                 if (strncmp(why, "Creating ", 9) == 0 ||
4037                     strncmp(why, "Continuing ", 11) == 0) {
4038                     gs_gamenum = gamenum;
4039                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4040                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4041                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4042 #if ZIPPY
4043                     if (appData.zippyPlay) {
4044                         ZippyGameStart(whitename, blackname);
4045                     }
4046 #endif /*ZIPPY*/
4047                     partnerBoardValid = FALSE; // [HGM] bughouse
4048                     continue;
4049                 }
4050
4051                 /* Game end messages */
4052                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4053                     ics_gamenum != gamenum) {
4054                     continue;
4055                 }
4056                 while (endtoken[0] == ' ') endtoken++;
4057                 switch (endtoken[0]) {
4058                   case '*':
4059                   default:
4060                     endtype = GameUnfinished;
4061                     break;
4062                   case '0':
4063                     endtype = BlackWins;
4064                     break;
4065                   case '1':
4066                     if (endtoken[1] == '/')
4067                       endtype = GameIsDrawn;
4068                     else
4069                       endtype = WhiteWins;
4070                     break;
4071                 }
4072                 GameEnds(endtype, why, GE_ICS);
4073 #if ZIPPY
4074                 if (appData.zippyPlay && first.initDone) {
4075                     ZippyGameEnd(endtype, why);
4076                     if (first.pr == NoProc) {
4077                       /* Start the next process early so that we'll
4078                          be ready for the next challenge */
4079                       StartChessProgram(&first);
4080                     }
4081                     /* Send "new" early, in case this command takes
4082                        a long time to finish, so that we'll be ready
4083                        for the next challenge. */
4084                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4085                     Reset(TRUE, TRUE);
4086                 }
4087 #endif /*ZIPPY*/
4088                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4089                 continue;
4090             }
4091
4092             if (looking_at(buf, &i, "Removing game * from observation") ||
4093                 looking_at(buf, &i, "no longer observing game *") ||
4094                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4095                 if (gameMode == IcsObserving &&
4096                     atoi(star_match[0]) == ics_gamenum)
4097                   {
4098                       /* icsEngineAnalyze */
4099                       if (appData.icsEngineAnalyze) {
4100                             ExitAnalyzeMode();
4101                             ModeHighlight();
4102                       }
4103                       StopClocks();
4104                       gameMode = IcsIdle;
4105                       ics_gamenum = -1;
4106                       ics_user_moved = FALSE;
4107                   }
4108                 continue;
4109             }
4110
4111             if (looking_at(buf, &i, "no longer examining game *")) {
4112                 if (gameMode == IcsExamining &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       gameMode = IcsIdle;
4116                       ics_gamenum = -1;
4117                       ics_user_moved = FALSE;
4118                   }
4119                 continue;
4120             }
4121
4122             /* Advance leftover_start past any newlines we find,
4123                so only partial lines can get reparsed */
4124             if (looking_at(buf, &i, "\n")) {
4125                 prevColor = curColor;
4126                 if (curColor != ColorNormal) {
4127                     if (oldi > next_out) {
4128                         SendToPlayer(&buf[next_out], oldi - next_out);
4129                         next_out = oldi;
4130                     }
4131                     Colorize(ColorNormal, FALSE);
4132                     curColor = ColorNormal;
4133                 }
4134                 if (started == STARTED_BOARD) {
4135                     started = STARTED_NONE;
4136                     parse[parse_pos] = NULLCHAR;
4137                     ParseBoard12(parse);
4138                     ics_user_moved = 0;
4139
4140                     /* Send premove here */
4141                     if (appData.premove) {
4142                       char str[MSG_SIZ];
4143                       if (currentMove == 0 &&
4144                           gameMode == IcsPlayingWhite &&
4145                           appData.premoveWhite) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (currentMove == 1 &&
4151                                  gameMode == IcsPlayingBlack &&
4152                                  appData.premoveBlack) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (gotPremove) {
4158                         gotPremove = 0;
4159                         ClearPremoveHighlights();
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                           UserMoveEvent(premoveFromX, premoveFromY,
4163                                         premoveToX, premoveToY,
4164                                         premovePromoChar);
4165                       }
4166                     }
4167
4168                     /* Usually suppress following prompt */
4169                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4170                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4171                         if (looking_at(buf, &i, "*% ")) {
4172                             savingComment = FALSE;
4173                             suppressKibitz = 0;
4174                         }
4175                     }
4176                     next_out = i;
4177                 } else if (started == STARTED_HOLDINGS) {
4178                     int gamenum;
4179                     char new_piece[MSG_SIZ];
4180                     started = STARTED_NONE;
4181                     parse[parse_pos] = NULLCHAR;
4182                     if (appData.debugMode)
4183                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4184                                                         parse, currentMove);
4185                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4186                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4187                         if (gameInfo.variant == VariantNormal) {
4188                           /* [HGM] We seem to switch variant during a game!
4189                            * Presumably no holdings were displayed, so we have
4190                            * to move the position two files to the right to
4191                            * create room for them!
4192                            */
4193                           VariantClass newVariant;
4194                           switch(gameInfo.boardWidth) { // base guess on board width
4195                                 case 9:  newVariant = VariantShogi; break;
4196                                 case 10: newVariant = VariantGreat; break;
4197                                 default: newVariant = VariantCrazyhouse; break;
4198                           }
4199                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4200                           /* Get a move list just to see the header, which
4201                              will tell us whether this is really bug or zh */
4202                           if (ics_getting_history == H_FALSE) {
4203                             ics_getting_history = H_REQUESTED;
4204                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4205                             SendToICS(str);
4206                           }
4207                         }
4208                         new_piece[0] = NULLCHAR;
4209                         sscanf(parse, "game %d white [%s black [%s <- %s",
4210                                &gamenum, white_holding, black_holding,
4211                                new_piece);
4212                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4213                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4214                         /* [HGM] copy holdings to board holdings area */
4215                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4216                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4217                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4218 #if ZIPPY
4219                         if (appData.zippyPlay && first.initDone) {
4220                             ZippyHoldings(white_holding, black_holding,
4221                                           new_piece);
4222                         }
4223 #endif /*ZIPPY*/
4224                         if (tinyLayout || smallLayout) {
4225                             char wh[16], bh[16];
4226                             PackHolding(wh, white_holding);
4227                             PackHolding(bh, black_holding);
4228                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4229                                     gameInfo.white, gameInfo.black);
4230                         } else {
4231                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4232                                     gameInfo.white, white_holding, _("vs."),
4233                                     gameInfo.black, black_holding);
4234                         }
4235                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4236                         DrawPosition(FALSE, boards[currentMove]);
4237                         DisplayTitle(str);
4238                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4239                         sscanf(parse, "game %d white [%s black [%s <- %s",
4240                                &gamenum, white_holding, black_holding,
4241                                new_piece);
4242                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4243                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4244                         /* [HGM] copy holdings to partner-board holdings area */
4245                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4246                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4247                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4248                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4249                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4250                       }
4251                     }
4252                     /* Suppress following prompt */
4253                     if (looking_at(buf, &i, "*% ")) {
4254                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4255                         savingComment = FALSE;
4256                         suppressKibitz = 0;
4257                     }
4258                     next_out = i;
4259                 }
4260                 continue;
4261             }
4262
4263             i++;                /* skip unparsed character and loop back */
4264         }
4265
4266         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4267 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4268 //          SendToPlayer(&buf[next_out], i - next_out);
4269             started != STARTED_HOLDINGS && leftover_start > next_out) {
4270             SendToPlayer(&buf[next_out], leftover_start - next_out);
4271             next_out = i;
4272         }
4273
4274         leftover_len = buf_len - leftover_start;
4275         /* if buffer ends with something we couldn't parse,
4276            reparse it after appending the next read */
4277
4278     } else if (count == 0) {
4279         RemoveInputSource(isr);
4280         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4281     } else {
4282         DisplayFatalError(_("Error reading from ICS"), error, 1);
4283     }
4284 }
4285
4286
4287 /* Board style 12 looks like this:
4288
4289    <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
4290
4291  * The "<12> " is stripped before it gets to this routine.  The two
4292  * trailing 0's (flip state and clock ticking) are later addition, and
4293  * some chess servers may not have them, or may have only the first.
4294  * Additional trailing fields may be added in the future.
4295  */
4296
4297 #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"
4298
4299 #define RELATION_OBSERVING_PLAYED    0
4300 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4301 #define RELATION_PLAYING_MYMOVE      1
4302 #define RELATION_PLAYING_NOTMYMOVE  -1
4303 #define RELATION_EXAMINING           2
4304 #define RELATION_ISOLATED_BOARD     -3
4305 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4306
4307 void
4308 ParseBoard12 (char *string)
4309 {
4310 #if ZIPPY
4311     int i, takeback;
4312     char *bookHit = NULL; // [HGM] book
4313 #endif
4314     GameMode newGameMode;
4315     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4316     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4317     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4318     char to_play, board_chars[200];
4319     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4320     char black[32], white[32];
4321     Board board;
4322     int prevMove = currentMove;
4323     int ticking = 2;
4324     ChessMove moveType;
4325     int fromX, fromY, toX, toY;
4326     char promoChar;
4327     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4328     Boolean weird = FALSE, reqFlag = FALSE;
4329
4330     fromX = fromY = toX = toY = -1;
4331
4332     newGame = FALSE;
4333
4334     if (appData.debugMode)
4335       fprintf(debugFP, "Parsing board: %s\n", string);
4336
4337     move_str[0] = NULLCHAR;
4338     elapsed_time[0] = NULLCHAR;
4339     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4340         int  i = 0, j;
4341         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4342             if(string[i] == ' ') { ranks++; files = 0; }
4343             else files++;
4344             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4345             i++;
4346         }
4347         for(j = 0; j <i; j++) board_chars[j] = string[j];
4348         board_chars[i] = '\0';
4349         string += i + 1;
4350     }
4351     n = sscanf(string, PATTERN, &to_play, &double_push,
4352                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4353                &gamenum, white, black, &relation, &basetime, &increment,
4354                &white_stren, &black_stren, &white_time, &black_time,
4355                &moveNum, str, elapsed_time, move_str, &ics_flip,
4356                &ticking);
4357
4358     if (n < 21) {
4359         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4360         DisplayError(str, 0);
4361         return;
4362     }
4363
4364     /* Convert the move number to internal form */
4365     moveNum = (moveNum - 1) * 2;
4366     if (to_play == 'B') moveNum++;
4367     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4369                         0, 1);
4370       return;
4371     }
4372
4373     switch (relation) {
4374       case RELATION_OBSERVING_PLAYED:
4375       case RELATION_OBSERVING_STATIC:
4376         if (gamenum == -1) {
4377             /* Old ICC buglet */
4378             relation = RELATION_OBSERVING_STATIC;
4379         }
4380         newGameMode = IcsObserving;
4381         break;
4382       case RELATION_PLAYING_MYMOVE:
4383       case RELATION_PLAYING_NOTMYMOVE:
4384         newGameMode =
4385           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4386             IcsPlayingWhite : IcsPlayingBlack;
4387         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4388         break;
4389       case RELATION_EXAMINING:
4390         newGameMode = IcsExamining;
4391         break;
4392       case RELATION_ISOLATED_BOARD:
4393       default:
4394         /* Just display this board.  If user was doing something else,
4395            we will forget about it until the next board comes. */
4396         newGameMode = IcsIdle;
4397         break;
4398       case RELATION_STARTING_POSITION:
4399         newGameMode = gameMode;
4400         break;
4401     }
4402
4403     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4404         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4405          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4406       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4407       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4408       static int lastBgGame = -1;
4409       char *toSqr;
4410       for (k = 0; k < ranks; k++) {
4411         for (j = 0; j < files; j++)
4412           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4413         if(gameInfo.holdingsWidth > 1) {
4414              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4415              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4416         }
4417       }
4418       CopyBoard(partnerBoard, board);
4419       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4420         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4421         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4422       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4423       if(toSqr = strchr(str, '-')) {
4424         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4425         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4427       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4428       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4429       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4430       if(twoBoards) {
4431           DisplayWhiteClock(white_time*fac, to_play == 'W');
4432           DisplayBlackClock(black_time*fac, to_play != 'W');
4433           activePartner = to_play;
4434           if(gamenum != lastBgGame) {
4435               char buf[MSG_SIZ];
4436               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4437               DisplayTitle(buf);
4438           }
4439           lastBgGame = gamenum;
4440           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4441                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4442       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4443                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4444       if(!twoBoards) DisplayMessage(partnerStatus, "");
4445         partnerBoardValid = TRUE;
4446       return;
4447     }
4448
4449     if(appData.dualBoard && appData.bgObserve) {
4450         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4451             SendToICS(ics_prefix), SendToICS("pobserve\n");
4452         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4453             char buf[MSG_SIZ];
4454             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4455             SendToICS(buf);
4456         }
4457     }
4458
4459     /* Modify behavior for initial board display on move listing
4460        of wild games.
4461        */
4462     switch (ics_getting_history) {
4463       case H_FALSE:
4464       case H_REQUESTED:
4465         break;
4466       case H_GOT_REQ_HEADER:
4467       case H_GOT_UNREQ_HEADER:
4468         /* This is the initial position of the current game */
4469         gamenum = ics_gamenum;
4470         moveNum = 0;            /* old ICS bug workaround */
4471         if (to_play == 'B') {
4472           startedFromSetupPosition = TRUE;
4473           blackPlaysFirst = TRUE;
4474           moveNum = 1;
4475           if (forwardMostMove == 0) forwardMostMove = 1;
4476           if (backwardMostMove == 0) backwardMostMove = 1;
4477           if (currentMove == 0) currentMove = 1;
4478         }
4479         newGameMode = gameMode;
4480         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4481         break;
4482       case H_GOT_UNWANTED_HEADER:
4483         /* This is an initial board that we don't want */
4484         return;
4485       case H_GETTING_MOVES:
4486         /* Should not happen */
4487         DisplayError(_("Error gathering move list: extra board"), 0);
4488         ics_getting_history = H_FALSE;
4489         return;
4490     }
4491
4492    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4493                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4494                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4495      /* [HGM] We seem to have switched variant unexpectedly
4496       * Try to guess new variant from board size
4497       */
4498           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4499           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4500           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4501           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4502           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4503           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4504           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4505           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4506           /* Get a move list just to see the header, which
4507              will tell us whether this is really bug or zh */
4508           if (ics_getting_history == H_FALSE) {
4509             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512           }
4513     }
4514
4515     /* Take action if this is the first board of a new game, or of a
4516        different game than is currently being displayed.  */
4517     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4518         relation == RELATION_ISOLATED_BOARD) {
4519
4520         /* Forget the old game and get the history (if any) of the new one */
4521         if (gameMode != BeginningOfGame) {
4522           Reset(TRUE, TRUE);
4523         }
4524         newGame = TRUE;
4525         if (appData.autoRaiseBoard) BoardToTop();
4526         prevMove = -3;
4527         if (gamenum == -1) {
4528             newGameMode = IcsIdle;
4529         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4530                    appData.getMoveList && !reqFlag) {
4531             /* Need to get game history */
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536
4537         /* Initially flip the board to have black on the bottom if playing
4538            black or if the ICS flip flag is set, but let the user change
4539            it with the Flip View button. */
4540         flipView = appData.autoFlipView ?
4541           (newGameMode == IcsPlayingBlack) || ics_flip :
4542           appData.flipView;
4543
4544         /* Done with values from previous mode; copy in new ones */
4545         gameMode = newGameMode;
4546         ModeHighlight();
4547         ics_gamenum = gamenum;
4548         if (gamenum == gs_gamenum) {
4549             int klen = strlen(gs_kind);
4550             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4551             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4552             gameInfo.event = StrSave(str);
4553         } else {
4554             gameInfo.event = StrSave("ICS game");
4555         }
4556         gameInfo.site = StrSave(appData.icsHost);
4557         gameInfo.date = PGNDate();
4558         gameInfo.round = StrSave("-");
4559         gameInfo.white = StrSave(white);
4560         gameInfo.black = StrSave(black);
4561         timeControl = basetime * 60 * 1000;
4562         timeControl_2 = 0;
4563         timeIncrement = increment * 1000;
4564         movesPerSession = 0;
4565         gameInfo.timeControl = TimeControlTagValue();
4566         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4567   if (appData.debugMode) {
4568     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4569     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4570     setbuf(debugFP, NULL);
4571   }
4572
4573         gameInfo.outOfBook = NULL;
4574
4575         /* Do we have the ratings? */
4576         if (strcmp(player1Name, white) == 0 &&
4577             strcmp(player2Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player1Rating, player2Rating);
4581             gameInfo.whiteRating = player1Rating;
4582             gameInfo.blackRating = player2Rating;
4583         } else if (strcmp(player2Name, white) == 0 &&
4584                    strcmp(player1Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player2Rating, player1Rating);
4588             gameInfo.whiteRating = player2Rating;
4589             gameInfo.blackRating = player1Rating;
4590         }
4591         player1Name[0] = player2Name[0] = NULLCHAR;
4592
4593         /* Silence shouts if requested */
4594         if (appData.quietPlay &&
4595             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4596             SendToICS(ics_prefix);
4597             SendToICS("set shout 0\n");
4598         }
4599     }
4600
4601     /* Deal with midgame name changes */
4602     if (!newGame) {
4603         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4604             if (gameInfo.white) free(gameInfo.white);
4605             gameInfo.white = StrSave(white);
4606         }
4607         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4608             if (gameInfo.black) free(gameInfo.black);
4609             gameInfo.black = StrSave(black);
4610         }
4611     }
4612
4613     /* Throw away game result if anything actually changes in examine mode */
4614     if (gameMode == IcsExamining && !newGame) {
4615         gameInfo.result = GameUnfinished;
4616         if (gameInfo.resultDetails != NULL) {
4617             free(gameInfo.resultDetails);
4618             gameInfo.resultDetails = NULL;
4619         }
4620     }
4621
4622     /* In pausing && IcsExamining mode, we ignore boards coming
4623        in if they are in a different variation than we are. */
4624     if (pauseExamInvalid) return;
4625     if (pausing && gameMode == IcsExamining) {
4626         if (moveNum <= pauseExamForwardMostMove) {
4627             pauseExamInvalid = TRUE;
4628             forwardMostMove = pauseExamForwardMostMove;
4629             return;
4630         }
4631     }
4632
4633   if (appData.debugMode) {
4634     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4635   }
4636     /* Parse the board */
4637     for (k = 0; k < ranks; k++) {
4638       for (j = 0; j < files; j++)
4639         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4640       if(gameInfo.holdingsWidth > 1) {
4641            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4642            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4643       }
4644     }
4645     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4646       board[5][BOARD_RGHT+1] = WhiteAngel;
4647       board[6][BOARD_RGHT+1] = WhiteMarshall;
4648       board[1][0] = BlackMarshall;
4649       board[2][0] = BlackAngel;
4650       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4651     }
4652     CopyBoard(boards[moveNum], board);
4653     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4654     if (moveNum == 0) {
4655         startedFromSetupPosition =
4656           !CompareBoards(board, initialPosition);
4657         if(startedFromSetupPosition)
4658             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4659     }
4660
4661     /* [HGM] Set castling rights. Take the outermost Rooks,
4662        to make it also work for FRC opening positions. Note that board12
4663        is really defective for later FRC positions, as it has no way to
4664        indicate which Rook can castle if they are on the same side of King.
4665        For the initial position we grant rights to the outermost Rooks,
4666        and remember thos rights, and we then copy them on positions
4667        later in an FRC game. This means WB might not recognize castlings with
4668        Rooks that have moved back to their original position as illegal,
4669        but in ICS mode that is not its job anyway.
4670     */
4671     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4672     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4673
4674         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675             if(board[0][i] == WhiteRook) j = i;
4676         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678             if(board[0][i] == WhiteRook) j = i;
4679         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4680         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4682         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4685         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686
4687         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4688         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4689         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4690             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4692             if(board[BOARD_HEIGHT-1][k] == bKing)
4693                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4694         if(gameInfo.variant == VariantTwoKings) {
4695             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4696             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4697             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4698         }
4699     } else { int r;
4700         r = boards[moveNum][CASTLING][0] = initialRights[0];
4701         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4702         r = boards[moveNum][CASTLING][1] = initialRights[1];
4703         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4704         r = boards[moveNum][CASTLING][3] = initialRights[3];
4705         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4706         r = boards[moveNum][CASTLING][4] = initialRights[4];
4707         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4708         /* wildcastle kludge: always assume King has rights */
4709         r = boards[moveNum][CASTLING][2] = initialRights[2];
4710         r = boards[moveNum][CASTLING][5] = initialRights[5];
4711     }
4712     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4713     boards[moveNum][EP_STATUS] = EP_NONE;
4714     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4715     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4716     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4717
4718
4719     if (ics_getting_history == H_GOT_REQ_HEADER ||
4720         ics_getting_history == H_GOT_UNREQ_HEADER) {
4721         /* This was an initial position from a move list, not
4722            the current position */
4723         return;
4724     }
4725
4726     /* Update currentMove and known move number limits */
4727     newMove = newGame || moveNum > forwardMostMove;
4728
4729     if (newGame) {
4730         forwardMostMove = backwardMostMove = currentMove = moveNum;
4731         if (gameMode == IcsExamining && moveNum == 0) {
4732           /* Workaround for ICS limitation: we are not told the wild
4733              type when starting to examine a game.  But if we ask for
4734              the move list, the move list header will tell us */
4735             ics_getting_history = H_REQUESTED;
4736             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4737             SendToICS(str);
4738         }
4739     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4740                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4741 #if ZIPPY
4742         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4743         /* [HGM] applied this also to an engine that is silently watching        */
4744         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4745             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4746             gameInfo.variant == currentlyInitializedVariant) {
4747           takeback = forwardMostMove - moveNum;
4748           for (i = 0; i < takeback; i++) {
4749             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4750             SendToProgram("undo\n", &first);
4751           }
4752         }
4753 #endif
4754
4755         forwardMostMove = moveNum;
4756         if (!pausing || currentMove > forwardMostMove)
4757           currentMove = forwardMostMove;
4758     } else {
4759         /* New part of history that is not contiguous with old part */
4760         if (pausing && gameMode == IcsExamining) {
4761             pauseExamInvalid = TRUE;
4762             forwardMostMove = pauseExamForwardMostMove;
4763             return;
4764         }
4765         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4766 #if ZIPPY
4767             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4768                 // [HGM] when we will receive the move list we now request, it will be
4769                 // fed to the engine from the first move on. So if the engine is not
4770                 // in the initial position now, bring it there.
4771                 InitChessProgram(&first, 0);
4772             }
4773 #endif
4774             ics_getting_history = H_REQUESTED;
4775             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4776             SendToICS(str);
4777         }
4778         forwardMostMove = backwardMostMove = currentMove = moveNum;
4779     }
4780
4781     /* Update the clocks */
4782     if (strchr(elapsed_time, '.')) {
4783       /* Time is in ms */
4784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4786     } else {
4787       /* Time is in seconds */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4790     }
4791
4792
4793 #if ZIPPY
4794     if (appData.zippyPlay && newGame &&
4795         gameMode != IcsObserving && gameMode != IcsIdle &&
4796         gameMode != IcsExamining)
4797       ZippyFirstBoard(moveNum, basetime, increment);
4798 #endif
4799
4800     /* Put the move on the move list, first converting
4801        to canonical algebraic form. */
4802     if (moveNum > 0) {
4803   if (appData.debugMode) {
4804     int f = forwardMostMove;
4805     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4806             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4807             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4808     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4809     fprintf(debugFP, "moveNum = %d\n", moveNum);
4810     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4811     setbuf(debugFP, NULL);
4812   }
4813         if (moveNum <= backwardMostMove) {
4814             /* We don't know what the board looked like before
4815                this move.  Punt. */
4816           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4817             strcat(parseList[moveNum - 1], " ");
4818             strcat(parseList[moveNum - 1], elapsed_time);
4819             moveList[moveNum - 1][0] = NULLCHAR;
4820         } else if (strcmp(move_str, "none") == 0) {
4821             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4822             /* Again, we don't know what the board looked like;
4823                this is really the start of the game. */
4824             parseList[moveNum - 1][0] = NULLCHAR;
4825             moveList[moveNum - 1][0] = NULLCHAR;
4826             backwardMostMove = moveNum;
4827             startedFromSetupPosition = TRUE;
4828             fromX = fromY = toX = toY = -1;
4829         } else {
4830           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4831           //                 So we parse the long-algebraic move string in stead of the SAN move
4832           int valid; char buf[MSG_SIZ], *prom;
4833
4834           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4835                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4836           // str looks something like "Q/a1-a2"; kill the slash
4837           if(str[1] == '/')
4838             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4839           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4840           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4841                 strcat(buf, prom); // long move lacks promo specification!
4842           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4843                 if(appData.debugMode)
4844                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4845                 safeStrCpy(move_str, buf, MSG_SIZ);
4846           }
4847           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4848                                 &fromX, &fromY, &toX, &toY, &promoChar)
4849                || ParseOneMove(buf, moveNum - 1, &moveType,
4850                                 &fromX, &fromY, &toX, &toY, &promoChar);
4851           // end of long SAN patch
4852           if (valid) {
4853             (void) CoordsToAlgebraic(boards[moveNum - 1],
4854                                      PosFlags(moveNum - 1),
4855                                      fromY, fromX, toY, toX, promoChar,
4856                                      parseList[moveNum-1]);
4857             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4858               case MT_NONE:
4859               case MT_STALEMATE:
4860               default:
4861                 break;
4862               case MT_CHECK:
4863                 if(!IS_SHOGI(gameInfo.variant))
4864                     strcat(parseList[moveNum - 1], "+");
4865                 break;
4866               case MT_CHECKMATE:
4867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4868                 strcat(parseList[moveNum - 1], "#");
4869                 break;
4870             }
4871             strcat(parseList[moveNum - 1], " ");
4872             strcat(parseList[moveNum - 1], elapsed_time);
4873             /* currentMoveString is set as a side-effect of ParseOneMove */
4874             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4875             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4876             strcat(moveList[moveNum - 1], "\n");
4877
4878             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4879                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4880               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4881                 ChessSquare old, new = boards[moveNum][k][j];
4882                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4883                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4884                   if(old == new) continue;
4885                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4886                   else if(new == WhiteWazir || new == BlackWazir) {
4887                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4888                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4889                       else boards[moveNum][k][j] = old; // preserve type of Gold
4890                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4891                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4892               }
4893           } else {
4894             /* Move from ICS was illegal!?  Punt. */
4895             if (appData.debugMode) {
4896               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4897               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4898             }
4899             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4900             strcat(parseList[moveNum - 1], " ");
4901             strcat(parseList[moveNum - 1], elapsed_time);
4902             moveList[moveNum - 1][0] = NULLCHAR;
4903             fromX = fromY = toX = toY = -1;
4904           }
4905         }
4906   if (appData.debugMode) {
4907     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4908     setbuf(debugFP, NULL);
4909   }
4910
4911 #if ZIPPY
4912         /* Send move to chess program (BEFORE animating it). */
4913         if (appData.zippyPlay && !newGame && newMove &&
4914            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4915
4916             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4917                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4918                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4919                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4920                             move_str);
4921                     DisplayError(str, 0);
4922                 } else {
4923                     if (first.sendTime) {
4924                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4925                     }
4926                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4927                     if (firstMove && !bookHit) {
4928                         firstMove = FALSE;
4929                         if (first.useColors) {
4930                           SendToProgram(gameMode == IcsPlayingWhite ?
4931                                         "white\ngo\n" :
4932                                         "black\ngo\n", &first);
4933                         } else {
4934                           SendToProgram("go\n", &first);
4935                         }
4936                         first.maybeThinking = TRUE;
4937                     }
4938                 }
4939             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4940               if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4942                 DisplayError(str, 0);
4943               } else {
4944                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4945                 SendMoveToProgram(moveNum - 1, &first);
4946               }
4947             }
4948         }
4949 #endif
4950     }
4951
4952     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4953         /* If move comes from a remote source, animate it.  If it
4954            isn't remote, it will have already been animated. */
4955         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4956             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4957         }
4958         if (!pausing && appData.highlightLastMove) {
4959             SetHighlights(fromX, fromY, toX, toY);
4960         }
4961     }
4962
4963     /* Start the clocks */
4964     whiteFlag = blackFlag = FALSE;
4965     appData.clockMode = !(basetime == 0 && increment == 0);
4966     if (ticking == 0) {
4967       ics_clock_paused = TRUE;
4968       StopClocks();
4969     } else if (ticking == 1) {
4970       ics_clock_paused = FALSE;
4971     }
4972     if (gameMode == IcsIdle ||
4973         relation == RELATION_OBSERVING_STATIC ||
4974         relation == RELATION_EXAMINING ||
4975         ics_clock_paused)
4976       DisplayBothClocks();
4977     else
4978       StartClocks();
4979
4980     /* Display opponents and material strengths */
4981     if (gameInfo.variant != VariantBughouse &&
4982         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4983         if (tinyLayout || smallLayout) {
4984             if(gameInfo.variant == VariantNormal)
4985               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4987                     basetime, increment);
4988             else
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment, (int) gameInfo.variant);
4992         } else {
4993             if(gameInfo.variant == VariantNormal)
4994               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4995                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4996                     basetime, increment);
4997             else
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment, VariantName(gameInfo.variant));
5001         }
5002         DisplayTitle(str);
5003   if (appData.debugMode) {
5004     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5005   }
5006     }
5007
5008
5009     /* Display the board */
5010     if (!pausing && !appData.noGUI) {
5011
5012       if (appData.premove)
5013           if (!gotPremove ||
5014              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5015              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5016               ClearPremoveHighlights();
5017
5018       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5019         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5020       DrawPosition(j, boards[currentMove]);
5021
5022       DisplayMove(moveNum - 1);
5023       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5024             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5025               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5026         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5027       }
5028     }
5029
5030     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5031 #if ZIPPY
5032     if(bookHit) { // [HGM] book: simulate book reply
5033         static char bookMove[MSG_SIZ]; // a bit generous?
5034
5035         programStats.nodes = programStats.depth = programStats.time =
5036         programStats.score = programStats.got_only_move = 0;
5037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5038
5039         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5040         strcat(bookMove, bookHit);
5041         HandleMachineMove(bookMove, &first);
5042     }
5043 #endif
5044 }
5045
5046 void
5047 GetMoveListEvent ()
5048 {
5049     char buf[MSG_SIZ];
5050     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5051         ics_getting_history = H_REQUESTED;
5052         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5053         SendToICS(buf);
5054     }
5055 }
5056
5057 void
5058 SendToBoth (char *msg)
5059 {   // to make it easy to keep two engines in step in dual analysis
5060     SendToProgram(msg, &first);
5061     if(second.analyzing) SendToProgram(msg, &second);
5062 }
5063
5064 void
5065 AnalysisPeriodicEvent (int force)
5066 {
5067     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5068          && !force) || !appData.periodicUpdates)
5069       return;
5070
5071     /* Send . command to Crafty to collect stats */
5072     SendToBoth(".\n");
5073
5074     /* Don't send another until we get a response (this makes
5075        us stop sending to old Crafty's which don't understand
5076        the "." command (sending illegal cmds resets node count & time,
5077        which looks bad)) */
5078     programStats.ok_to_send = 0;
5079 }
5080
5081 void
5082 ics_update_width (int new_width)
5083 {
5084         ics_printf("set width %d\n", new_width);
5085 }
5086
5087 void
5088 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5089 {
5090     char buf[MSG_SIZ];
5091
5092     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5093         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5094             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5095             SendToProgram(buf, cps);
5096             return;
5097         }
5098         // null move in variant where engine does not understand it (for analysis purposes)
5099         SendBoard(cps, moveNum + 1); // send position after move in stead.
5100         return;
5101     }
5102     if (cps->useUsermove) {
5103       SendToProgram("usermove ", cps);
5104     }
5105     if (cps->useSAN) {
5106       char *space;
5107       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5108         int len = space - parseList[moveNum];
5109         memcpy(buf, parseList[moveNum], len);
5110         buf[len++] = '\n';
5111         buf[len] = NULLCHAR;
5112       } else {
5113         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5114       }
5115       SendToProgram(buf, cps);
5116     } else {
5117       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5118         AlphaRank(moveList[moveNum], 4);
5119         SendToProgram(moveList[moveNum], cps);
5120         AlphaRank(moveList[moveNum], 4); // and back
5121       } else
5122       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5123        * the engine. It would be nice to have a better way to identify castle
5124        * moves here. */
5125       if(appData.fischerCastling && cps->useOOCastle) {
5126         int fromX = moveList[moveNum][0] - AAA;
5127         int fromY = moveList[moveNum][1] - ONE;
5128         int toX = moveList[moveNum][2] - AAA;
5129         int toY = moveList[moveNum][3] - ONE;
5130         if((boards[moveNum][fromY][fromX] == WhiteKing
5131             && boards[moveNum][toY][toX] == WhiteRook)
5132            || (boards[moveNum][fromY][fromX] == BlackKing
5133                && boards[moveNum][toY][toX] == BlackRook)) {
5134           if(toX > fromX) SendToProgram("O-O\n", cps);
5135           else SendToProgram("O-O-O\n", cps);
5136         }
5137         else SendToProgram(moveList[moveNum], cps);
5138       } else
5139       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5140           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5143                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5144           SendToProgram(buf, cps);
5145       } else
5146       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5147         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5148           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5149           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5150                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5151         } else
5152           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5153                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5154         SendToProgram(buf, cps);
5155       }
5156       else SendToProgram(moveList[moveNum], cps);
5157       /* End of additions by Tord */
5158     }
5159
5160     /* [HGM] setting up the opening has brought engine in force mode! */
5161     /*       Send 'go' if we are in a mode where machine should play. */
5162     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5163         (gameMode == TwoMachinesPlay   ||
5164 #if ZIPPY
5165          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5166 #endif
5167          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5168         SendToProgram("go\n", cps);
5169   if (appData.debugMode) {
5170     fprintf(debugFP, "(extra)\n");
5171   }
5172     }
5173     setboardSpoiledMachineBlack = 0;
5174 }
5175
5176 void
5177 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5178 {
5179     char user_move[MSG_SIZ];
5180     char suffix[4];
5181
5182     if(gameInfo.variant == VariantSChess && promoChar) {
5183         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5184         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5185     } else suffix[0] = NULLCHAR;
5186
5187     switch (moveType) {
5188       default:
5189         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5190                 (int)moveType, fromX, fromY, toX, toY);
5191         DisplayError(user_move + strlen("say "), 0);
5192         break;
5193       case WhiteKingSideCastle:
5194       case BlackKingSideCastle:
5195       case WhiteQueenSideCastleWild:
5196       case BlackQueenSideCastleWild:
5197       /* PUSH Fabien */
5198       case WhiteHSideCastleFR:
5199       case BlackHSideCastleFR:
5200       /* POP Fabien */
5201         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5202         break;
5203       case WhiteQueenSideCastle:
5204       case BlackQueenSideCastle:
5205       case WhiteKingSideCastleWild:
5206       case BlackKingSideCastleWild:
5207       /* PUSH Fabien */
5208       case WhiteASideCastleFR:
5209       case BlackASideCastleFR:
5210       /* POP Fabien */
5211         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5212         break;
5213       case WhiteNonPromotion:
5214       case BlackNonPromotion:
5215         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5216         break;
5217       case WhitePromotion:
5218       case BlackPromotion:
5219         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5220            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5221           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5223                 PieceToChar(WhiteFerz));
5224         else if(gameInfo.variant == VariantGreat)
5225           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteMan));
5228         else
5229           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 promoChar);
5232         break;
5233       case WhiteDrop:
5234       case BlackDrop:
5235       drop:
5236         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5237                  ToUpper(PieceToChar((ChessSquare) fromX)),
5238                  AAA + toX, ONE + toY);
5239         break;
5240       case IllegalMove:  /* could be a variant we don't quite understand */
5241         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5242       case NormalMove:
5243       case WhiteCapturesEnPassant:
5244       case BlackCapturesEnPassant:
5245         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5247         break;
5248     }
5249     SendToICS(user_move);
5250     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5251         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5252 }
5253
5254 void
5255 UploadGameEvent ()
5256 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5257     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5258     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5259     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5260       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5261       return;
5262     }
5263     if(gameMode != IcsExamining) { // is this ever not the case?
5264         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5265
5266         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5267           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5268         } else { // on FICS we must first go to general examine mode
5269           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5270         }
5271         if(gameInfo.variant != VariantNormal) {
5272             // try figure out wild number, as xboard names are not always valid on ICS
5273             for(i=1; i<=36; i++) {
5274               snprintf(buf, MSG_SIZ, "wild/%d", i);
5275                 if(StringToVariant(buf) == gameInfo.variant) break;
5276             }
5277             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5278             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5279             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5280         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5281         SendToICS(ics_prefix);
5282         SendToICS(buf);
5283         if(startedFromSetupPosition || backwardMostMove != 0) {
5284           fen = PositionToFEN(backwardMostMove, NULL, 1);
5285           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5286             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5287             SendToICS(buf);
5288           } else { // FICS: everything has to set by separate bsetup commands
5289             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5290             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5291             SendToICS(buf);
5292             if(!WhiteOnMove(backwardMostMove)) {
5293                 SendToICS("bsetup tomove black\n");
5294             }
5295             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5296             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5297             SendToICS(buf);
5298             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5299             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5300             SendToICS(buf);
5301             i = boards[backwardMostMove][EP_STATUS];
5302             if(i >= 0) { // set e.p.
5303               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5304                 SendToICS(buf);
5305             }
5306             bsetup++;
5307           }
5308         }
5309       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5310             SendToICS("bsetup done\n"); // switch to normal examining.
5311     }
5312     for(i = backwardMostMove; i<last; i++) {
5313         char buf[20];
5314         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5315         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5316             int len = strlen(moveList[i]);
5317             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5318             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5319         }
5320         SendToICS(buf);
5321     }
5322     SendToICS(ics_prefix);
5323     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5324 }
5325
5326 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5327
5328 void
5329 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5330 {
5331     if (rf == DROP_RANK) {
5332       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5333       sprintf(move, "%c@%c%c\n",
5334                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5335     } else {
5336         if (promoChar == 'x' || promoChar == NULLCHAR) {
5337           sprintf(move, "%c%c%c%c\n",
5338                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5339           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5340         } else {
5341             sprintf(move, "%c%c%c%c%c\n",
5342                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5343         }
5344     }
5345 }
5346
5347 void
5348 ProcessICSInitScript (FILE *f)
5349 {
5350     char buf[MSG_SIZ];
5351
5352     while (fgets(buf, MSG_SIZ, f)) {
5353         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5354     }
5355
5356     fclose(f);
5357 }
5358
5359
5360 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5361 int dragging;
5362 static ClickType lastClickType;
5363
5364 int
5365 Partner (ChessSquare *p)
5366 { // change piece into promotion partner if one shogi-promotes to the other
5367   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5368   ChessSquare partner;
5369   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5370   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5371   *p = partner;
5372   return 1;
5373 }
5374
5375 void
5376 Sweep (int step)
5377 {
5378     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5379     static int toggleFlag;
5380     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5381     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5382     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5383     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5384     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5385     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5386     do {
5387         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5388         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5389         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5390         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5391         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5392         if(!step) step = -1;
5393     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5394             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5395             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5396             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5397     if(toX >= 0) {
5398         int victim = boards[currentMove][toY][toX];
5399         boards[currentMove][toY][toX] = promoSweep;
5400         DrawPosition(FALSE, boards[currentMove]);
5401         boards[currentMove][toY][toX] = victim;
5402     } else
5403     ChangeDragPiece(promoSweep);
5404 }
5405
5406 int
5407 PromoScroll (int x, int y)
5408 {
5409   int step = 0;
5410
5411   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5412   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5413   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5414   if(!step) return FALSE;
5415   lastX = x; lastY = y;
5416   if((promoSweep < BlackPawn) == flipView) step = -step;
5417   if(step > 0) selectFlag = 1;
5418   if(!selectFlag) Sweep(step);
5419   return FALSE;
5420 }
5421
5422 void
5423 NextPiece (int step)
5424 {
5425     ChessSquare piece = boards[currentMove][toY][toX];
5426     do {
5427         pieceSweep -= step;
5428         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5429         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5430         if(!step) step = -1;
5431     } while(PieceToChar(pieceSweep) == '.');
5432     boards[currentMove][toY][toX] = pieceSweep;
5433     DrawPosition(FALSE, boards[currentMove]);
5434     boards[currentMove][toY][toX] = piece;
5435 }
5436 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5437 void
5438 AlphaRank (char *move, int n)
5439 {
5440 //    char *p = move, c; int x, y;
5441
5442     if (appData.debugMode) {
5443         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5444     }
5445
5446     if(move[1]=='*' &&
5447        move[2]>='0' && move[2]<='9' &&
5448        move[3]>='a' && move[3]<='x'    ) {
5449         move[1] = '@';
5450         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5451         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5452     } else
5453     if(move[0]>='0' && move[0]<='9' &&
5454        move[1]>='a' && move[1]<='x' &&
5455        move[2]>='0' && move[2]<='9' &&
5456        move[3]>='a' && move[3]<='x'    ) {
5457         /* input move, Shogi -> normal */
5458         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5459         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5460         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5462     } else
5463     if(move[1]=='@' &&
5464        move[3]>='0' && move[3]<='9' &&
5465        move[2]>='a' && move[2]<='x'    ) {
5466         move[1] = '*';
5467         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5468         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5469     } else
5470     if(
5471        move[0]>='a' && move[0]<='x' &&
5472        move[3]>='0' && move[3]<='9' &&
5473        move[2]>='a' && move[2]<='x'    ) {
5474          /* output move, normal -> Shogi */
5475         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5476         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5477         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5478         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5479         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5480     }
5481     if (appData.debugMode) {
5482         fprintf(debugFP, "   out = '%s'\n", move);
5483     }
5484 }
5485
5486 char yy_textstr[8000];
5487
5488 /* Parser for moves from gnuchess, ICS, or user typein box */
5489 Boolean
5490 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5491 {
5492     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5493
5494     switch (*moveType) {
5495       case WhitePromotion:
5496       case BlackPromotion:
5497       case WhiteNonPromotion:
5498       case BlackNonPromotion:
5499       case NormalMove:
5500       case FirstLeg:
5501       case WhiteCapturesEnPassant:
5502       case BlackCapturesEnPassant:
5503       case WhiteKingSideCastle:
5504       case WhiteQueenSideCastle:
5505       case BlackKingSideCastle:
5506       case BlackQueenSideCastle:
5507       case WhiteKingSideCastleWild:
5508       case WhiteQueenSideCastleWild:
5509       case BlackKingSideCastleWild:
5510       case BlackQueenSideCastleWild:
5511       /* Code added by Tord: */
5512       case WhiteHSideCastleFR:
5513       case WhiteASideCastleFR:
5514       case BlackHSideCastleFR:
5515       case BlackASideCastleFR:
5516       /* End of code added by Tord */
5517       case IllegalMove:         /* bug or odd chess variant */
5518         *fromX = currentMoveString[0] - AAA;
5519         *fromY = currentMoveString[1] - ONE;
5520         *toX = currentMoveString[2] - AAA;
5521         *toY = currentMoveString[3] - ONE;
5522         *promoChar = currentMoveString[4];
5523         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5524             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5525     if (appData.debugMode) {
5526         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5527     }
5528             *fromX = *fromY = *toX = *toY = 0;
5529             return FALSE;
5530         }
5531         if (appData.testLegality) {
5532           return (*moveType != IllegalMove);
5533         } else {
5534           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5535                          // [HGM] lion: if this is a double move we are less critical
5536                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5537         }
5538
5539       case WhiteDrop:
5540       case BlackDrop:
5541         *fromX = *moveType == WhiteDrop ?
5542           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5543           (int) CharToPiece(ToLower(currentMoveString[0]));
5544         *fromY = DROP_RANK;
5545         *toX = currentMoveString[2] - AAA;
5546         *toY = currentMoveString[3] - ONE;
5547         *promoChar = NULLCHAR;
5548         return TRUE;
5549
5550       case AmbiguousMove:
5551       case ImpossibleMove:
5552       case EndOfFile:
5553       case ElapsedTime:
5554       case Comment:
5555       case PGNTag:
5556       case NAG:
5557       case WhiteWins:
5558       case BlackWins:
5559       case GameIsDrawn:
5560       default:
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5563     }
5564         /* bug? */
5565         *fromX = *fromY = *toX = *toY = 0;
5566         *promoChar = NULLCHAR;
5567         return FALSE;
5568     }
5569 }
5570
5571 Boolean pushed = FALSE;
5572 char *lastParseAttempt;
5573
5574 void
5575 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5576 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5577   int fromX, fromY, toX, toY; char promoChar;
5578   ChessMove moveType;
5579   Boolean valid;
5580   int nr = 0;
5581
5582   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5583   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5584     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5585     pushed = TRUE;
5586   }
5587   endPV = forwardMostMove;
5588   do {
5589     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5590     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5591     lastParseAttempt = pv;
5592     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5593     if(!valid && nr == 0 &&
5594        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5595         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5596         // Hande case where played move is different from leading PV move
5597         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5598         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5599         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5600         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5601           endPV += 2; // if position different, keep this
5602           moveList[endPV-1][0] = fromX + AAA;
5603           moveList[endPV-1][1] = fromY + ONE;
5604           moveList[endPV-1][2] = toX + AAA;
5605           moveList[endPV-1][3] = toY + ONE;
5606           parseList[endPV-1][0] = NULLCHAR;
5607           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5608         }
5609       }
5610     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5611     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5612     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5613     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5614         valid++; // allow comments in PV
5615         continue;
5616     }
5617     nr++;
5618     if(endPV+1 > framePtr) break; // no space, truncate
5619     if(!valid) break;
5620     endPV++;
5621     CopyBoard(boards[endPV], boards[endPV-1]);
5622     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5623     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5624     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5625     CoordsToAlgebraic(boards[endPV - 1],
5626                              PosFlags(endPV - 1),
5627                              fromY, fromX, toY, toX, promoChar,
5628                              parseList[endPV - 1]);
5629   } while(valid);
5630   if(atEnd == 2) return; // used hidden, for PV conversion
5631   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5632   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5633   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5634                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5635   DrawPosition(TRUE, boards[currentMove]);
5636 }
5637
5638 int
5639 MultiPV (ChessProgramState *cps)
5640 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5641         int i;
5642         for(i=0; i<cps->nrOptions; i++)
5643             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5644                 return i;
5645         return -1;
5646 }
5647
5648 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5649
5650 Boolean
5651 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5652 {
5653         int startPV, multi, lineStart, origIndex = index;
5654         char *p, buf2[MSG_SIZ];
5655         ChessProgramState *cps = (pane ? &second : &first);
5656
5657         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5658         lastX = x; lastY = y;
5659         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5660         lineStart = startPV = index;
5661         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5662         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5663         index = startPV;
5664         do{ while(buf[index] && buf[index] != '\n') index++;
5665         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5666         buf[index] = 0;
5667         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5668                 int n = cps->option[multi].value;
5669                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5670                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5671                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5672                 cps->option[multi].value = n;
5673                 *start = *end = 0;
5674                 return FALSE;
5675         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5676                 ExcludeClick(origIndex - lineStart);
5677                 return FALSE;
5678         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5679                 Collapse(origIndex - lineStart);
5680                 return FALSE;
5681         }
5682         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5683         *start = startPV; *end = index-1;
5684         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5685         return TRUE;
5686 }
5687
5688 char *
5689 PvToSAN (char *pv)
5690 {
5691         static char buf[10*MSG_SIZ];
5692         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5693         *buf = NULLCHAR;
5694         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5695         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5696         for(i = forwardMostMove; i<endPV; i++){
5697             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5698             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5699             k += strlen(buf+k);
5700         }
5701         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5702         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5703         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5704         endPV = savedEnd;
5705         return buf;
5706 }
5707
5708 Boolean
5709 LoadPV (int x, int y)
5710 { // called on right mouse click to load PV
5711   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5712   lastX = x; lastY = y;
5713   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5714   extendGame = FALSE;
5715   return TRUE;
5716 }
5717
5718 void
5719 UnLoadPV ()
5720 {
5721   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5722   if(endPV < 0) return;
5723   if(appData.autoCopyPV) CopyFENToClipboard();
5724   endPV = -1;
5725   if(extendGame && currentMove > forwardMostMove) {
5726         Boolean saveAnimate = appData.animate;
5727         if(pushed) {
5728             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5729                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5730             } else storedGames--; // abandon shelved tail of original game
5731         }
5732         pushed = FALSE;
5733         forwardMostMove = currentMove;
5734         currentMove = oldFMM;
5735         appData.animate = FALSE;
5736         ToNrEvent(forwardMostMove);
5737         appData.animate = saveAnimate;
5738   }
5739   currentMove = forwardMostMove;
5740   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5741   ClearPremoveHighlights();
5742   DrawPosition(TRUE, boards[currentMove]);
5743 }
5744
5745 void
5746 MovePV (int x, int y, int h)
5747 { // step through PV based on mouse coordinates (called on mouse move)
5748   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5749
5750   // we must somehow check if right button is still down (might be released off board!)
5751   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5752   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5753   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5754   if(!step) return;
5755   lastX = x; lastY = y;
5756
5757   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5758   if(endPV < 0) return;
5759   if(y < margin) step = 1; else
5760   if(y > h - margin) step = -1;
5761   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5762   currentMove += step;
5763   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5764   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5765                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5766   DrawPosition(FALSE, boards[currentMove]);
5767 }
5768
5769
5770 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5771 // All positions will have equal probability, but the current method will not provide a unique
5772 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5773 #define DARK 1
5774 #define LITE 2
5775 #define ANY 3
5776
5777 int squaresLeft[4];
5778 int piecesLeft[(int)BlackPawn];
5779 int seed, nrOfShuffles;
5780
5781 void
5782 GetPositionNumber ()
5783 {       // sets global variable seed
5784         int i;
5785
5786         seed = appData.defaultFrcPosition;
5787         if(seed < 0) { // randomize based on time for negative FRC position numbers
5788                 for(i=0; i<50; i++) seed += random();
5789                 seed = random() ^ random() >> 8 ^ random() << 8;
5790                 if(seed<0) seed = -seed;
5791         }
5792 }
5793
5794 int
5795 put (Board board, int pieceType, int rank, int n, int shade)
5796 // put the piece on the (n-1)-th empty squares of the given shade
5797 {
5798         int i;
5799
5800         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5801                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5802                         board[rank][i] = (ChessSquare) pieceType;
5803                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5804                         squaresLeft[ANY]--;
5805                         piecesLeft[pieceType]--;
5806                         return i;
5807                 }
5808         }
5809         return -1;
5810 }
5811
5812
5813 void
5814 AddOnePiece (Board board, int pieceType, int rank, int shade)
5815 // calculate where the next piece goes, (any empty square), and put it there
5816 {
5817         int i;
5818
5819         i = seed % squaresLeft[shade];
5820         nrOfShuffles *= squaresLeft[shade];
5821         seed /= squaresLeft[shade];
5822         put(board, pieceType, rank, i, shade);
5823 }
5824
5825 void
5826 AddTwoPieces (Board board, int pieceType, int rank)
5827 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5828 {
5829         int i, n=squaresLeft[ANY], j=n-1, k;
5830
5831         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5832         i = seed % k;  // pick one
5833         nrOfShuffles *= k;
5834         seed /= k;
5835         while(i >= j) i -= j--;
5836         j = n - 1 - j; i += j;
5837         put(board, pieceType, rank, j, ANY);
5838         put(board, pieceType, rank, i, ANY);
5839 }
5840
5841 void
5842 SetUpShuffle (Board board, int number)
5843 {
5844         int i, p, first=1;
5845
5846         GetPositionNumber(); nrOfShuffles = 1;
5847
5848         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5849         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5850         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5851
5852         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5853
5854         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5855             p = (int) board[0][i];
5856             if(p < (int) BlackPawn) piecesLeft[p] ++;
5857             board[0][i] = EmptySquare;
5858         }
5859
5860         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5861             // shuffles restricted to allow normal castling put KRR first
5862             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5863                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5864             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5865                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5866             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5867                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5868             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5869                 put(board, WhiteRook, 0, 0, ANY);
5870             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5871         }
5872
5873         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5874             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5875             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5876                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5877                 while(piecesLeft[p] >= 2) {
5878                     AddOnePiece(board, p, 0, LITE);
5879                     AddOnePiece(board, p, 0, DARK);
5880                 }
5881                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5882             }
5883
5884         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5885             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5886             // but we leave King and Rooks for last, to possibly obey FRC restriction
5887             if(p == (int)WhiteRook) continue;
5888             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5889             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5890         }
5891
5892         // now everything is placed, except perhaps King (Unicorn) and Rooks
5893
5894         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5895             // Last King gets castling rights
5896             while(piecesLeft[(int)WhiteUnicorn]) {
5897                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5898                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5899             }
5900
5901             while(piecesLeft[(int)WhiteKing]) {
5902                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906
5907         } else {
5908             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5909             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5910         }
5911
5912         // Only Rooks can be left; simply place them all
5913         while(piecesLeft[(int)WhiteRook]) {
5914                 i = put(board, WhiteRook, 0, 0, ANY);
5915                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5916                         if(first) {
5917                                 first=0;
5918                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5919                         }
5920                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5921                 }
5922         }
5923         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5924             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5925         }
5926
5927         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5928 }
5929
5930 int
5931 SetCharTable (char *table, const char * map)
5932 /* [HGM] moved here from winboard.c because of its general usefulness */
5933 /*       Basically a safe strcpy that uses the last character as King */
5934 {
5935     int result = FALSE; int NrPieces;
5936
5937     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5938                     && NrPieces >= 12 && !(NrPieces&1)) {
5939         int i; /* [HGM] Accept even length from 12 to 34 */
5940
5941         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5942         for( i=0; i<NrPieces/2-1; i++ ) {
5943             table[i] = map[i];
5944             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5945         }
5946         table[(int) WhiteKing]  = map[NrPieces/2-1];
5947         table[(int) BlackKing]  = map[NrPieces-1];
5948
5949         result = TRUE;
5950     }
5951
5952     return result;
5953 }
5954
5955 void
5956 Prelude (Board board)
5957 {       // [HGM] superchess: random selection of exo-pieces
5958         int i, j, k; ChessSquare p;
5959         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5960
5961         GetPositionNumber(); // use FRC position number
5962
5963         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5964             SetCharTable(pieceToChar, appData.pieceToCharTable);
5965             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5966                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5967         }
5968
5969         j = seed%4;                 seed /= 4;
5970         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5971         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5972         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5973         j = seed%3 + (seed%3 >= j); seed /= 3;
5974         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5975         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5976         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5977         j = seed%3;                 seed /= 3;
5978         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5979         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5980         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5981         j = seed%2 + (seed%2 >= j); seed /= 2;
5982         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5983         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5984         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5985         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5986         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5987         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5988         put(board, exoPieces[0],    0, 0, ANY);
5989         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5990 }
5991
5992 void
5993 InitPosition (int redraw)
5994 {
5995     ChessSquare (* pieces)[BOARD_FILES];
5996     int i, j, pawnRow=1, pieceRows=1, overrule,
5997     oldx = gameInfo.boardWidth,
5998     oldy = gameInfo.boardHeight,
5999     oldh = gameInfo.holdingsWidth;
6000     static int oldv;
6001
6002     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6003
6004     /* [AS] Initialize pv info list [HGM] and game status */
6005     {
6006         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6007             pvInfoList[i].depth = 0;
6008             boards[i][EP_STATUS] = EP_NONE;
6009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6010         }
6011
6012         initialRulePlies = 0; /* 50-move counter start */
6013
6014         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6015         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6016     }
6017
6018
6019     /* [HGM] logic here is completely changed. In stead of full positions */
6020     /* the initialized data only consist of the two backranks. The switch */
6021     /* selects which one we will use, which is than copied to the Board   */
6022     /* initialPosition, which for the rest is initialized by Pawns and    */
6023     /* empty squares. This initial position is then copied to boards[0],  */
6024     /* possibly after shuffling, so that it remains available.            */
6025
6026     gameInfo.holdingsWidth = 0; /* default board sizes */
6027     gameInfo.boardWidth    = 8;
6028     gameInfo.boardHeight   = 8;
6029     gameInfo.holdingsSize  = 0;
6030     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6031     for(i=0; i<BOARD_FILES-2; i++)
6032       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6033     initialPosition[EP_STATUS] = EP_NONE;
6034     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6035     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6036          SetCharTable(pieceNickName, appData.pieceNickNames);
6037     else SetCharTable(pieceNickName, "............");
6038     pieces = FIDEArray;
6039
6040     switch (gameInfo.variant) {
6041     case VariantFischeRandom:
6042       shuffleOpenings = TRUE;
6043       appData.fischerCastling = TRUE;
6044     default:
6045       break;
6046     case VariantShatranj:
6047       pieces = ShatranjArray;
6048       nrCastlingRights = 0;
6049       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6050       break;
6051     case VariantMakruk:
6052       pieces = makrukArray;
6053       nrCastlingRights = 0;
6054       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6055       break;
6056     case VariantASEAN:
6057       pieces = aseanArray;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6060       break;
6061     case VariantTwoKings:
6062       pieces = twoKingsArray;
6063       break;
6064     case VariantGrand:
6065       pieces = GrandArray;
6066       nrCastlingRights = 0;
6067       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6068       gameInfo.boardWidth = 10;
6069       gameInfo.boardHeight = 10;
6070       gameInfo.holdingsSize = 7;
6071       break;
6072     case VariantCapaRandom:
6073       shuffleOpenings = TRUE;
6074       appData.fischerCastling = TRUE;
6075     case VariantCapablanca:
6076       pieces = CapablancaArray;
6077       gameInfo.boardWidth = 10;
6078       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6079       break;
6080     case VariantGothic:
6081       pieces = GothicArray;
6082       gameInfo.boardWidth = 10;
6083       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6084       break;
6085     case VariantSChess:
6086       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6087       gameInfo.holdingsSize = 7;
6088       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6089       break;
6090     case VariantJanus:
6091       pieces = JanusArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6094       nrCastlingRights = 6;
6095         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6096         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6097         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6098         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6099         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6100         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6101       break;
6102     case VariantFalcon:
6103       pieces = FalconArray;
6104       gameInfo.boardWidth = 10;
6105       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6106       break;
6107     case VariantXiangqi:
6108       pieces = XiangqiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 10;
6111       nrCastlingRights = 0;
6112       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6113       break;
6114     case VariantShogi:
6115       pieces = ShogiArray;
6116       gameInfo.boardWidth  = 9;
6117       gameInfo.boardHeight = 9;
6118       gameInfo.holdingsSize = 7;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6121       break;
6122     case VariantChu:
6123       pieces = ChuArray; pieceRows = 3;
6124       gameInfo.boardWidth  = 12;
6125       gameInfo.boardHeight = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6128                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6129       break;
6130     case VariantCourier:
6131       pieces = CourierArray;
6132       gameInfo.boardWidth  = 12;
6133       nrCastlingRights = 0;
6134       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6135       break;
6136     case VariantKnightmate:
6137       pieces = KnightmateArray;
6138       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6139       break;
6140     case VariantSpartan:
6141       pieces = SpartanArray;
6142       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6143       break;
6144     case VariantLion:
6145       pieces = lionArray;
6146       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6147       break;
6148     case VariantChuChess:
6149       pieces = ChuChessArray;
6150       gameInfo.boardWidth = 10;
6151       gameInfo.boardHeight = 10;
6152       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6153       break;
6154     case VariantFairy:
6155       pieces = fairyArray;
6156       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6157       break;
6158     case VariantGreat:
6159       pieces = GreatArray;
6160       gameInfo.boardWidth = 10;
6161       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6162       gameInfo.holdingsSize = 8;
6163       break;
6164     case VariantSuper:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6167       gameInfo.holdingsSize = 8;
6168       startedFromSetupPosition = TRUE;
6169       break;
6170     case VariantCrazyhouse:
6171     case VariantBughouse:
6172       pieces = FIDEArray;
6173       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6174       gameInfo.holdingsSize = 5;
6175       break;
6176     case VariantWildCastle:
6177       pieces = FIDEArray;
6178       /* !!?shuffle with kings guaranteed to be on d or e file */
6179       shuffleOpenings = 1;
6180       break;
6181     case VariantNoCastle:
6182       pieces = FIDEArray;
6183       nrCastlingRights = 0;
6184       /* !!?unconstrained back-rank shuffle */
6185       shuffleOpenings = 1;
6186       break;
6187     }
6188
6189     overrule = 0;
6190     if(appData.NrFiles >= 0) {
6191         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6192         gameInfo.boardWidth = appData.NrFiles;
6193     }
6194     if(appData.NrRanks >= 0) {
6195         gameInfo.boardHeight = appData.NrRanks;
6196     }
6197     if(appData.holdingsSize >= 0) {
6198         i = appData.holdingsSize;
6199         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6200         gameInfo.holdingsSize = i;
6201     }
6202     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6203     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6204         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6205
6206     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6207     if(pawnRow < 1) pawnRow = 1;
6208     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6209        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6210     if(gameInfo.variant == VariantChu) pawnRow = 3;
6211
6212     /* User pieceToChar list overrules defaults */
6213     if(appData.pieceToCharTable != NULL)
6214         SetCharTable(pieceToChar, appData.pieceToCharTable);
6215
6216     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6217
6218         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6219             s = (ChessSquare) 0; /* account holding counts in guard band */
6220         for( i=0; i<BOARD_HEIGHT; i++ )
6221             initialPosition[i][j] = s;
6222
6223         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6224         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6225         initialPosition[pawnRow][j] = WhitePawn;
6226         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6227         if(gameInfo.variant == VariantXiangqi) {
6228             if(j&1) {
6229                 initialPosition[pawnRow][j] =
6230                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6231                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6232                    initialPosition[2][j] = WhiteCannon;
6233                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6234                 }
6235             }
6236         }
6237         if(gameInfo.variant == VariantChu) {
6238              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6239                initialPosition[pawnRow+1][j] = WhiteCobra,
6240                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6241              for(i=1; i<pieceRows; i++) {
6242                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6243                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6244              }
6245         }
6246         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6247             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6248                initialPosition[0][j] = WhiteRook;
6249                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6250             }
6251         }
6252         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6253     }
6254     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6255     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6256
6257             j=BOARD_LEFT+1;
6258             initialPosition[1][j] = WhiteBishop;
6259             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6260             j=BOARD_RGHT-2;
6261             initialPosition[1][j] = WhiteRook;
6262             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6263     }
6264
6265     if( nrCastlingRights == -1) {
6266         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6267         /*       This sets default castling rights from none to normal corners   */
6268         /* Variants with other castling rights must set them themselves above    */
6269         nrCastlingRights = 6;
6270
6271         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6272         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6273         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6274         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6275         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6276         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6277      }
6278
6279      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6280      if(gameInfo.variant == VariantGreat) { // promotion commoners
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6282         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6284         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6285      }
6286      if( gameInfo.variant == VariantSChess ) {
6287       initialPosition[1][0] = BlackMarshall;
6288       initialPosition[2][0] = BlackAngel;
6289       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6290       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6291       initialPosition[1][1] = initialPosition[2][1] =
6292       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6293      }
6294   if (appData.debugMode) {
6295     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6296   }
6297     if(shuffleOpenings) {
6298         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6299         startedFromSetupPosition = TRUE;
6300     }
6301     if(startedFromPositionFile) {
6302       /* [HGM] loadPos: use PositionFile for every new game */
6303       CopyBoard(initialPosition, filePosition);
6304       for(i=0; i<nrCastlingRights; i++)
6305           initialRights[i] = filePosition[CASTLING][i];
6306       startedFromSetupPosition = TRUE;
6307     }
6308
6309     CopyBoard(boards[0], initialPosition);
6310
6311     if(oldx != gameInfo.boardWidth ||
6312        oldy != gameInfo.boardHeight ||
6313        oldv != gameInfo.variant ||
6314        oldh != gameInfo.holdingsWidth
6315                                          )
6316             InitDrawingSizes(-2 ,0);
6317
6318     oldv = gameInfo.variant;
6319     if (redraw)
6320       DrawPosition(TRUE, boards[currentMove]);
6321 }
6322
6323 void
6324 SendBoard (ChessProgramState *cps, int moveNum)
6325 {
6326     char message[MSG_SIZ];
6327
6328     if (cps->useSetboard) {
6329       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6330       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6331       SendToProgram(message, cps);
6332       free(fen);
6333
6334     } else {
6335       ChessSquare *bp;
6336       int i, j, left=0, right=BOARD_WIDTH;
6337       /* Kludge to set black to move, avoiding the troublesome and now
6338        * deprecated "black" command.
6339        */
6340       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6341         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6342
6343       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6344
6345       SendToProgram("edit\n", cps);
6346       SendToProgram("#\n", cps);
6347       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6348         bp = &boards[moveNum][i][left];
6349         for (j = left; j < right; j++, bp++) {
6350           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6351           if ((int) *bp < (int) BlackPawn) {
6352             if(j == BOARD_RGHT+1)
6353                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6354             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6355             if(message[0] == '+' || message[0] == '~') {
6356               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6357                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6358                         AAA + j, ONE + i);
6359             }
6360             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6361                 message[1] = BOARD_RGHT   - 1 - j + '1';
6362                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6363             }
6364             SendToProgram(message, cps);
6365           }
6366         }
6367       }
6368
6369       SendToProgram("c\n", cps);
6370       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6371         bp = &boards[moveNum][i][left];
6372         for (j = left; j < right; j++, bp++) {
6373           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6374           if (((int) *bp != (int) EmptySquare)
6375               && ((int) *bp >= (int) BlackPawn)) {
6376             if(j == BOARD_LEFT-2)
6377                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6378             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6379                     AAA + j, ONE + i);
6380             if(message[0] == '+' || message[0] == '~') {
6381               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6382                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6383                         AAA + j, ONE + i);
6384             }
6385             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6386                 message[1] = BOARD_RGHT   - 1 - j + '1';
6387                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6388             }
6389             SendToProgram(message, cps);
6390           }
6391         }
6392       }
6393
6394       SendToProgram(".\n", cps);
6395     }
6396     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6397 }
6398
6399 char exclusionHeader[MSG_SIZ];
6400 int exCnt, excludePtr;
6401 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6402 static Exclusion excluTab[200];
6403 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6404
6405 static void
6406 WriteMap (int s)
6407 {
6408     int j;
6409     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6410     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6411 }
6412
6413 static void
6414 ClearMap ()
6415 {
6416     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6417     excludePtr = 24; exCnt = 0;
6418     WriteMap(0);
6419 }
6420
6421 static void
6422 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6423 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6424     char buf[2*MOVE_LEN], *p;
6425     Exclusion *e = excluTab;
6426     int i;
6427     for(i=0; i<exCnt; i++)
6428         if(e[i].ff == fromX && e[i].fr == fromY &&
6429            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6430     if(i == exCnt) { // was not in exclude list; add it
6431         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6432         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6433             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6434             return; // abort
6435         }
6436         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6437         excludePtr++; e[i].mark = excludePtr++;
6438         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6439         exCnt++;
6440     }
6441     exclusionHeader[e[i].mark] = state;
6442 }
6443
6444 static int
6445 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6446 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6447     char buf[MSG_SIZ];
6448     int j, k;
6449     ChessMove moveType;
6450     if((signed char)promoChar == -1) { // kludge to indicate best move
6451         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6452             return 1; // if unparsable, abort
6453     }
6454     // update exclusion map (resolving toggle by consulting existing state)
6455     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6456     j = k%8; k >>= 3;
6457     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6458     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6459          excludeMap[k] |=   1<<j;
6460     else excludeMap[k] &= ~(1<<j);
6461     // update header
6462     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6463     // inform engine
6464     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6465     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6466     SendToBoth(buf);
6467     return (state == '+');
6468 }
6469
6470 static void
6471 ExcludeClick (int index)
6472 {
6473     int i, j;
6474     Exclusion *e = excluTab;
6475     if(index < 25) { // none, best or tail clicked
6476         if(index < 13) { // none: include all
6477             WriteMap(0); // clear map
6478             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6479             SendToBoth("include all\n"); // and inform engine
6480         } else if(index > 18) { // tail
6481             if(exclusionHeader[19] == '-') { // tail was excluded
6482                 SendToBoth("include all\n");
6483                 WriteMap(0); // clear map completely
6484                 // now re-exclude selected moves
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6487             } else { // tail was included or in mixed state
6488                 SendToBoth("exclude all\n");
6489                 WriteMap(0xFF); // fill map completely
6490                 // now re-include selected moves
6491                 j = 0; // count them
6492                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6493                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6494                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6495             }
6496         } else { // best
6497             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6498         }
6499     } else {
6500         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6501             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6502             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6503             break;
6504         }
6505     }
6506 }
6507
6508 ChessSquare
6509 DefaultPromoChoice (int white)
6510 {
6511     ChessSquare result;
6512     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6513        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6514         result = WhiteFerz; // no choice
6515     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6516         result= WhiteKing; // in Suicide Q is the last thing we want
6517     else if(gameInfo.variant == VariantSpartan)
6518         result = white ? WhiteQueen : WhiteAngel;
6519     else result = WhiteQueen;
6520     if(!white) result = WHITE_TO_BLACK result;
6521     return result;
6522 }
6523
6524 static int autoQueen; // [HGM] oneclick
6525
6526 int
6527 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6528 {
6529     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6530     /* [HGM] add Shogi promotions */
6531     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6532     ChessSquare piece, partner;
6533     ChessMove moveType;
6534     Boolean premove;
6535
6536     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6537     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6538
6539     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6540       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6541         return FALSE;
6542
6543     piece = boards[currentMove][fromY][fromX];
6544     if(gameInfo.variant == VariantChu) {
6545         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6546         promotionZoneSize = BOARD_HEIGHT/3;
6547         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6548     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6549         promotionZoneSize = BOARD_HEIGHT/3;
6550         highestPromotingPiece = (int)WhiteAlfil;
6551     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6552         promotionZoneSize = 3;
6553     }
6554
6555     // Treat Lance as Pawn when it is not representing Amazon or Lance
6556     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6557         if(piece == WhiteLance) piece = WhitePawn; else
6558         if(piece == BlackLance) piece = BlackPawn;
6559     }
6560
6561     // next weed out all moves that do not touch the promotion zone at all
6562     if((int)piece >= BlackPawn) {
6563         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6564              return FALSE;
6565         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6566         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6567     } else {
6568         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6569            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6570         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6571              return FALSE;
6572     }
6573
6574     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6575
6576     // weed out mandatory Shogi promotions
6577     if(gameInfo.variant == VariantShogi) {
6578         if(piece >= BlackPawn) {
6579             if(toY == 0 && piece == BlackPawn ||
6580                toY == 0 && piece == BlackQueen ||
6581                toY <= 1 && piece == BlackKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         } else {
6586             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6587                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6588                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6589                 *promoChoice = '+';
6590                 return FALSE;
6591             }
6592         }
6593     }
6594
6595     // weed out obviously illegal Pawn moves
6596     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6597         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6598         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6599         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6600         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6601         // note we are not allowed to test for valid (non-)capture, due to premove
6602     }
6603
6604     // we either have a choice what to promote to, or (in Shogi) whether to promote
6605     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6606        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6607         ChessSquare p=BlackFerz;  // no choice
6608         while(p < EmptySquare) {  //but make sure we use piece that exists
6609             *promoChoice = PieceToChar(p++);
6610             if(*promoChoice != '.') break;
6611         }
6612         return FALSE;
6613     }
6614     // no sense asking what we must promote to if it is going to explode...
6615     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6616         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6617         return FALSE;
6618     }
6619     // give caller the default choice even if we will not make it
6620     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6621     partner = piece; // pieces can promote if the pieceToCharTable says so
6622     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6623     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6624     if(        sweepSelect && gameInfo.variant != VariantGreat
6625                            && gameInfo.variant != VariantGrand
6626                            && gameInfo.variant != VariantSuper) return FALSE;
6627     if(autoQueen) return FALSE; // predetermined
6628
6629     // suppress promotion popup on illegal moves that are not premoves
6630     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6631               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6632     if(appData.testLegality && !premove) {
6633         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6634                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6635         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6636         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6637             return FALSE;
6638     }
6639
6640     return TRUE;
6641 }
6642
6643 int
6644 InPalace (int row, int column)
6645 {   /* [HGM] for Xiangqi */
6646     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6647          column < (BOARD_WIDTH + 4)/2 &&
6648          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6649     return FALSE;
6650 }
6651
6652 int
6653 PieceForSquare (int x, int y)
6654 {
6655   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6656      return -1;
6657   else
6658      return boards[currentMove][y][x];
6659 }
6660
6661 int
6662 OKToStartUserMove (int x, int y)
6663 {
6664     ChessSquare from_piece;
6665     int white_piece;
6666
6667     if (matchMode) return FALSE;
6668     if (gameMode == EditPosition) return TRUE;
6669
6670     if (x >= 0 && y >= 0)
6671       from_piece = boards[currentMove][y][x];
6672     else
6673       from_piece = EmptySquare;
6674
6675     if (from_piece == EmptySquare) return FALSE;
6676
6677     white_piece = (int)from_piece >= (int)WhitePawn &&
6678       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6679
6680     switch (gameMode) {
6681       case AnalyzeFile:
6682       case TwoMachinesPlay:
6683       case EndOfGame:
6684         return FALSE;
6685
6686       case IcsObserving:
6687       case IcsIdle:
6688         return FALSE;
6689
6690       case MachinePlaysWhite:
6691       case IcsPlayingBlack:
6692         if (appData.zippyPlay) return FALSE;
6693         if (white_piece) {
6694             DisplayMoveError(_("You are playing Black"));
6695             return FALSE;
6696         }
6697         break;
6698
6699       case MachinePlaysBlack:
6700       case IcsPlayingWhite:
6701         if (appData.zippyPlay) return FALSE;
6702         if (!white_piece) {
6703             DisplayMoveError(_("You are playing White"));
6704             return FALSE;
6705         }
6706         break;
6707
6708       case PlayFromGameFile:
6709             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6710       case EditGame:
6711         if (!white_piece && WhiteOnMove(currentMove)) {
6712             DisplayMoveError(_("It is White's turn"));
6713             return FALSE;
6714         }
6715         if (white_piece && !WhiteOnMove(currentMove)) {
6716             DisplayMoveError(_("It is Black's turn"));
6717             return FALSE;
6718         }
6719         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6720             /* Editing correspondence game history */
6721             /* Could disallow this or prompt for confirmation */
6722             cmailOldMove = -1;
6723         }
6724         break;
6725
6726       case BeginningOfGame:
6727         if (appData.icsActive) return FALSE;
6728         if (!appData.noChessProgram) {
6729             if (!white_piece) {
6730                 DisplayMoveError(_("You are playing White"));
6731                 return FALSE;
6732             }
6733         }
6734         break;
6735
6736       case Training:
6737         if (!white_piece && WhiteOnMove(currentMove)) {
6738             DisplayMoveError(_("It is White's turn"));
6739             return FALSE;
6740         }
6741         if (white_piece && !WhiteOnMove(currentMove)) {
6742             DisplayMoveError(_("It is Black's turn"));
6743             return FALSE;
6744         }
6745         break;
6746
6747       default:
6748       case IcsExamining:
6749         break;
6750     }
6751     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6752         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6753         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6754         && gameMode != AnalyzeFile && gameMode != Training) {
6755         DisplayMoveError(_("Displayed position is not current"));
6756         return FALSE;
6757     }
6758     return TRUE;
6759 }
6760
6761 Boolean
6762 OnlyMove (int *x, int *y, Boolean captures)
6763 {
6764     DisambiguateClosure cl;
6765     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6766     switch(gameMode) {
6767       case MachinePlaysBlack:
6768       case IcsPlayingWhite:
6769       case BeginningOfGame:
6770         if(!WhiteOnMove(currentMove)) return FALSE;
6771         break;
6772       case MachinePlaysWhite:
6773       case IcsPlayingBlack:
6774         if(WhiteOnMove(currentMove)) return FALSE;
6775         break;
6776       case EditGame:
6777         break;
6778       default:
6779         return FALSE;
6780     }
6781     cl.pieceIn = EmptySquare;
6782     cl.rfIn = *y;
6783     cl.ffIn = *x;
6784     cl.rtIn = -1;
6785     cl.ftIn = -1;
6786     cl.promoCharIn = NULLCHAR;
6787     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6788     if( cl.kind == NormalMove ||
6789         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6790         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6791         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6792       fromX = cl.ff;
6793       fromY = cl.rf;
6794       *x = cl.ft;
6795       *y = cl.rt;
6796       return TRUE;
6797     }
6798     if(cl.kind != ImpossibleMove) return FALSE;
6799     cl.pieceIn = EmptySquare;
6800     cl.rfIn = -1;
6801     cl.ffIn = -1;
6802     cl.rtIn = *y;
6803     cl.ftIn = *x;
6804     cl.promoCharIn = NULLCHAR;
6805     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6806     if( cl.kind == NormalMove ||
6807         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6808         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6809         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6810       fromX = cl.ff;
6811       fromY = cl.rf;
6812       *x = cl.ft;
6813       *y = cl.rt;
6814       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6815       return TRUE;
6816     }
6817     return FALSE;
6818 }
6819
6820 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6821 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6822 int lastLoadGameUseList = FALSE;
6823 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6824 ChessMove lastLoadGameStart = EndOfFile;
6825 int doubleClick;
6826 Boolean addToBookFlag;
6827
6828 void
6829 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6830 {
6831     ChessMove moveType;
6832     ChessSquare pup;
6833     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6834
6835     /* Check if the user is playing in turn.  This is complicated because we
6836        let the user "pick up" a piece before it is his turn.  So the piece he
6837        tried to pick up may have been captured by the time he puts it down!
6838        Therefore we use the color the user is supposed to be playing in this
6839        test, not the color of the piece that is currently on the starting
6840        square---except in EditGame mode, where the user is playing both
6841        sides; fortunately there the capture race can't happen.  (It can
6842        now happen in IcsExamining mode, but that's just too bad.  The user
6843        will get a somewhat confusing message in that case.)
6844        */
6845
6846     switch (gameMode) {
6847       case AnalyzeFile:
6848       case TwoMachinesPlay:
6849       case EndOfGame:
6850       case IcsObserving:
6851       case IcsIdle:
6852         /* We switched into a game mode where moves are not accepted,
6853            perhaps while the mouse button was down. */
6854         return;
6855
6856       case MachinePlaysWhite:
6857         /* User is moving for Black */
6858         if (WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is White's turn"));
6860             return;
6861         }
6862         break;
6863
6864       case MachinePlaysBlack:
6865         /* User is moving for White */
6866         if (!WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return;
6869         }
6870         break;
6871
6872       case PlayFromGameFile:
6873             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6874       case EditGame:
6875       case IcsExamining:
6876       case BeginningOfGame:
6877       case AnalyzeMode:
6878       case Training:
6879         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6880         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6881             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6882             /* User is moving for Black */
6883             if (WhiteOnMove(currentMove)) {
6884                 DisplayMoveError(_("It is White's turn"));
6885                 return;
6886             }
6887         } else {
6888             /* User is moving for White */
6889             if (!WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is Black's turn"));
6891                 return;
6892             }
6893         }
6894         break;
6895
6896       case IcsPlayingBlack:
6897         /* User is moving for Black */
6898         if (WhiteOnMove(currentMove)) {
6899             if (!appData.premove) {
6900                 DisplayMoveError(_("It is White's turn"));
6901             } else if (toX >= 0 && toY >= 0) {
6902                 premoveToX = toX;
6903                 premoveToY = toY;
6904                 premoveFromX = fromX;
6905                 premoveFromY = fromY;
6906                 premovePromoChar = promoChar;
6907                 gotPremove = 1;
6908                 if (appData.debugMode)
6909                     fprintf(debugFP, "Got premove: fromX %d,"
6910                             "fromY %d, toX %d, toY %d\n",
6911                             fromX, fromY, toX, toY);
6912             }
6913             return;
6914         }
6915         break;
6916
6917       case IcsPlayingWhite:
6918         /* User is moving for White */
6919         if (!WhiteOnMove(currentMove)) {
6920             if (!appData.premove) {
6921                 DisplayMoveError(_("It is Black's turn"));
6922             } else if (toX >= 0 && toY >= 0) {
6923                 premoveToX = toX;
6924                 premoveToY = toY;
6925                 premoveFromX = fromX;
6926                 premoveFromY = fromY;
6927                 premovePromoChar = promoChar;
6928                 gotPremove = 1;
6929                 if (appData.debugMode)
6930                     fprintf(debugFP, "Got premove: fromX %d,"
6931                             "fromY %d, toX %d, toY %d\n",
6932                             fromX, fromY, toX, toY);
6933             }
6934             return;
6935         }
6936         break;
6937
6938       default:
6939         break;
6940
6941       case EditPosition:
6942         /* EditPosition, empty square, or different color piece;
6943            click-click move is possible */
6944         if (toX == -2 || toY == -2) {
6945             boards[0][fromY][fromX] = EmptySquare;
6946             DrawPosition(FALSE, boards[currentMove]);
6947             return;
6948         } else if (toX >= 0 && toY >= 0) {
6949             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6950                 ChessSquare q, p = boards[0][rf][ff];
6951                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6952                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6953                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6954                 if(PieceToChar(q) == '+') gatingPiece = p;
6955             }
6956             boards[0][toY][toX] = boards[0][fromY][fromX];
6957             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6958                 if(boards[0][fromY][0] != EmptySquare) {
6959                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6960                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6961                 }
6962             } else
6963             if(fromX == BOARD_RGHT+1) {
6964                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6965                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6966                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6967                 }
6968             } else
6969             boards[0][fromY][fromX] = gatingPiece;
6970             DrawPosition(FALSE, boards[currentMove]);
6971             return;
6972         }
6973         return;
6974     }
6975
6976     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6977     pup = boards[currentMove][toY][toX];
6978
6979     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6980     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6981          if( pup != EmptySquare ) return;
6982          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6983            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6984                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6985            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6986            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6987            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6988            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6989          fromY = DROP_RANK;
6990     }
6991
6992     /* [HGM] always test for legality, to get promotion info */
6993     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6994                                          fromY, fromX, toY, toX, promoChar);
6995
6996     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6997
6998     /* [HGM] but possibly ignore an IllegalMove result */
6999     if (appData.testLegality) {
7000         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7001             DisplayMoveError(_("Illegal move"));
7002             return;
7003         }
7004     }
7005
7006     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7007         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7008              ClearPremoveHighlights(); // was included
7009         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7010         return;
7011     }
7012
7013     if(addToBookFlag) { // adding moves to book
7014         char buf[MSG_SIZ], move[MSG_SIZ];
7015         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7016         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7017         AddBookMove(buf);
7018         addToBookFlag = FALSE;
7019         ClearHighlights();
7020         return;
7021     }
7022
7023     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7024 }
7025
7026 /* Common tail of UserMoveEvent and DropMenuEvent */
7027 int
7028 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7029 {
7030     char *bookHit = 0;
7031
7032     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7033         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7034         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7035         if(WhiteOnMove(currentMove)) {
7036             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7037         } else {
7038             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7039         }
7040     }
7041
7042     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7043        move type in caller when we know the move is a legal promotion */
7044     if(moveType == NormalMove && promoChar)
7045         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7046
7047     /* [HGM] <popupFix> The following if has been moved here from
7048        UserMoveEvent(). Because it seemed to belong here (why not allow
7049        piece drops in training games?), and because it can only be
7050        performed after it is known to what we promote. */
7051     if (gameMode == Training) {
7052       /* compare the move played on the board to the next move in the
7053        * game. If they match, display the move and the opponent's response.
7054        * If they don't match, display an error message.
7055        */
7056       int saveAnimate;
7057       Board testBoard;
7058       CopyBoard(testBoard, boards[currentMove]);
7059       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7060
7061       if (CompareBoards(testBoard, boards[currentMove+1])) {
7062         ForwardInner(currentMove+1);
7063
7064         /* Autoplay the opponent's response.
7065          * if appData.animate was TRUE when Training mode was entered,
7066          * the response will be animated.
7067          */
7068         saveAnimate = appData.animate;
7069         appData.animate = animateTraining;
7070         ForwardInner(currentMove+1);
7071         appData.animate = saveAnimate;
7072
7073         /* check for the end of the game */
7074         if (currentMove >= forwardMostMove) {
7075           gameMode = PlayFromGameFile;
7076           ModeHighlight();
7077           SetTrainingModeOff();
7078           DisplayInformation(_("End of game"));
7079         }
7080       } else {
7081         DisplayError(_("Incorrect move"), 0);
7082       }
7083       return 1;
7084     }
7085
7086   /* Ok, now we know that the move is good, so we can kill
7087      the previous line in Analysis Mode */
7088   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7089                                 && currentMove < forwardMostMove) {
7090     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7091     else forwardMostMove = currentMove;
7092   }
7093
7094   ClearMap();
7095
7096   /* If we need the chess program but it's dead, restart it */
7097   ResurrectChessProgram();
7098
7099   /* A user move restarts a paused game*/
7100   if (pausing)
7101     PauseEvent();
7102
7103   thinkOutput[0] = NULLCHAR;
7104
7105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7106
7107   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7108     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7109     return 1;
7110   }
7111
7112   if (gameMode == BeginningOfGame) {
7113     if (appData.noChessProgram) {
7114       gameMode = EditGame;
7115       SetGameInfo();
7116     } else {
7117       char buf[MSG_SIZ];
7118       gameMode = MachinePlaysBlack;
7119       StartClocks();
7120       SetGameInfo();
7121       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7122       DisplayTitle(buf);
7123       if (first.sendName) {
7124         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7125         SendToProgram(buf, &first);
7126       }
7127       StartClocks();
7128     }
7129     ModeHighlight();
7130   }
7131
7132   /* Relay move to ICS or chess engine */
7133   if (appData.icsActive) {
7134     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7135         gameMode == IcsExamining) {
7136       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7137         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7138         SendToICS("draw ");
7139         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7140       }
7141       // also send plain move, in case ICS does not understand atomic claims
7142       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7143       ics_user_moved = 1;
7144     }
7145   } else {
7146     if (first.sendTime && (gameMode == BeginningOfGame ||
7147                            gameMode == MachinePlaysWhite ||
7148                            gameMode == MachinePlaysBlack)) {
7149       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7150     }
7151     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7152          // [HGM] book: if program might be playing, let it use book
7153         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7154         first.maybeThinking = TRUE;
7155     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7156         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7157         SendBoard(&first, currentMove+1);
7158         if(second.analyzing) {
7159             if(!second.useSetboard) SendToProgram("undo\n", &second);
7160             SendBoard(&second, currentMove+1);
7161         }
7162     } else {
7163         SendMoveToProgram(forwardMostMove-1, &first);
7164         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7165     }
7166     if (currentMove == cmailOldMove + 1) {
7167       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7168     }
7169   }
7170
7171   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7172
7173   switch (gameMode) {
7174   case EditGame:
7175     if(appData.testLegality)
7176     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7177     case MT_NONE:
7178     case MT_CHECK:
7179       break;
7180     case MT_CHECKMATE:
7181     case MT_STAINMATE:
7182       if (WhiteOnMove(currentMove)) {
7183         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7184       } else {
7185         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7186       }
7187       break;
7188     case MT_STALEMATE:
7189       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7190       break;
7191     }
7192     break;
7193
7194   case MachinePlaysBlack:
7195   case MachinePlaysWhite:
7196     /* disable certain menu options while machine is thinking */
7197     SetMachineThinkingEnables();
7198     break;
7199
7200   default:
7201     break;
7202   }
7203
7204   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7205   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7206
7207   if(bookHit) { // [HGM] book: simulate book reply
7208         static char bookMove[MSG_SIZ]; // a bit generous?
7209
7210         programStats.nodes = programStats.depth = programStats.time =
7211         programStats.score = programStats.got_only_move = 0;
7212         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7213
7214         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7215         strcat(bookMove, bookHit);
7216         HandleMachineMove(bookMove, &first);
7217   }
7218   return 1;
7219 }
7220
7221 void
7222 MarkByFEN(char *fen)
7223 {
7224         int r, f;
7225         if(!appData.markers || !appData.highlightDragging) return;
7226         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7227         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7228         while(*fen) {
7229             int s = 0;
7230             marker[r][f] = 0;
7231             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7232             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7233             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7234             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7235             if(*fen == 'T') marker[r][f++] = 0; else
7236             if(*fen == 'Y') marker[r][f++] = 1; else
7237             if(*fen == 'G') marker[r][f++] = 3; else
7238             if(*fen == 'B') marker[r][f++] = 4; else
7239             if(*fen == 'C') marker[r][f++] = 5; else
7240             if(*fen == 'M') marker[r][f++] = 6; else
7241             if(*fen == 'W') marker[r][f++] = 7; else
7242             if(*fen == 'D') marker[r][f++] = 8; else
7243             if(*fen == 'R') marker[r][f++] = 2; else {
7244                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7245               f += s; fen -= s>0;
7246             }
7247             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7248             if(r < 0) break;
7249             fen++;
7250         }
7251         DrawPosition(TRUE, NULL);
7252 }
7253
7254 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7255
7256 void
7257 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7258 {
7259     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7260     Markers *m = (Markers *) closure;
7261     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7262         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7263                          || kind == WhiteCapturesEnPassant
7264                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7265     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7266 }
7267
7268 static int hoverSavedValid;
7269
7270 void
7271 MarkTargetSquares (int clear)
7272 {
7273   int x, y, sum=0;
7274   if(clear) { // no reason to ever suppress clearing
7275     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7276     hoverSavedValid = 0;
7277     if(!sum) return; // nothing was cleared,no redraw needed
7278   } else {
7279     int capt = 0;
7280     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7281        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7282     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7283     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7284       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7285       if(capt)
7286       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7287     }
7288   }
7289   DrawPosition(FALSE, NULL);
7290 }
7291
7292 int
7293 Explode (Board board, int fromX, int fromY, int toX, int toY)
7294 {
7295     if(gameInfo.variant == VariantAtomic &&
7296        (board[toY][toX] != EmptySquare ||                     // capture?
7297         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7298                          board[fromY][fromX] == BlackPawn   )
7299       )) {
7300         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7301         return TRUE;
7302     }
7303     return FALSE;
7304 }
7305
7306 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7307
7308 int
7309 CanPromote (ChessSquare piece, int y)
7310 {
7311         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7312         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7313         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7314         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7315            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7316            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7317          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7318         return (piece == BlackPawn && y <= zone ||
7319                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7320                 piece == BlackLance && y == 1 ||
7321                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7322 }
7323
7324 void
7325 HoverEvent (int xPix, int yPix, int x, int y)
7326 {
7327         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7328         int r, f;
7329         if(!first.highlight) return;
7330         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7331         if(x == oldX && y == oldY) return; // only do something if we enter new square
7332         oldFromX = fromX; oldFromY = fromY;
7333         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7334           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7335             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7336           hoverSavedValid = 1;
7337         } else if(oldX != x || oldY != y) {
7338           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7339           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7342           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7343             char buf[MSG_SIZ];
7344             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7345             SendToProgram(buf, &first);
7346           }
7347           oldX = x; oldY = y;
7348 //        SetHighlights(fromX, fromY, x, y);
7349         }
7350 }
7351
7352 void ReportClick(char *action, int x, int y)
7353 {
7354         char buf[MSG_SIZ]; // Inform engine of what user does
7355         int r, f;
7356         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7357           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7358             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7359         if(!first.highlight || gameMode == EditPosition) return;
7360         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7361         SendToProgram(buf, &first);
7362 }
7363
7364 void
7365 LeftClick (ClickType clickType, int xPix, int yPix)
7366 {
7367     int x, y;
7368     Boolean saveAnimate;
7369     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7370     char promoChoice = NULLCHAR;
7371     ChessSquare piece;
7372     static TimeMark lastClickTime, prevClickTime;
7373
7374     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7375
7376     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7377
7378     if (clickType == Press) ErrorPopDown();
7379     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7380
7381     x = EventToSquare(xPix, BOARD_WIDTH);
7382     y = EventToSquare(yPix, BOARD_HEIGHT);
7383     if (!flipView && y >= 0) {
7384         y = BOARD_HEIGHT - 1 - y;
7385     }
7386     if (flipView && x >= 0) {
7387         x = BOARD_WIDTH - 1 - x;
7388     }
7389
7390     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7391         defaultPromoChoice = promoSweep;
7392         promoSweep = EmptySquare;   // terminate sweep
7393         promoDefaultAltered = TRUE;
7394         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7395     }
7396
7397     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7398         if(clickType == Release) return; // ignore upclick of click-click destination
7399         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7400         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7401         if(gameInfo.holdingsWidth &&
7402                 (WhiteOnMove(currentMove)
7403                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7404                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7405             // click in right holdings, for determining promotion piece
7406             ChessSquare p = boards[currentMove][y][x];
7407             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7408             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7409             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7410                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7411                 fromX = fromY = -1;
7412                 return;
7413             }
7414         }
7415         DrawPosition(FALSE, boards[currentMove]);
7416         return;
7417     }
7418
7419     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7420     if(clickType == Press
7421             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7422               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7423               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7424         return;
7425
7426     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7427         // could be static click on premove from-square: abort premove
7428         gotPremove = 0;
7429         ClearPremoveHighlights();
7430     }
7431
7432     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7433         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7434
7435     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7436         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7437                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7438         defaultPromoChoice = DefaultPromoChoice(side);
7439     }
7440
7441     autoQueen = appData.alwaysPromoteToQueen;
7442
7443     if (fromX == -1) {
7444       int originalY = y;
7445       gatingPiece = EmptySquare;
7446       if (clickType != Press) {
7447         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7448             DragPieceEnd(xPix, yPix); dragging = 0;
7449             DrawPosition(FALSE, NULL);
7450         }
7451         return;
7452       }
7453       doubleClick = FALSE;
7454       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7455         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7456       }
7457       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7458       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7459          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7460          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7461             /* First square */
7462             if (OKToStartUserMove(fromX, fromY)) {
7463                 second = 0;
7464                 ReportClick("lift", x, y);
7465                 MarkTargetSquares(0);
7466                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7467                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7468                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7469                     promoSweep = defaultPromoChoice;
7470                     selectFlag = 0; lastX = xPix; lastY = yPix;
7471                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7472                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7473                 }
7474                 if (appData.highlightDragging) {
7475                     SetHighlights(fromX, fromY, -1, -1);
7476                 } else {
7477                     ClearHighlights();
7478                 }
7479             } else fromX = fromY = -1;
7480             return;
7481         }
7482     }
7483
7484     /* fromX != -1 */
7485     if (clickType == Press && gameMode != EditPosition) {
7486         ChessSquare fromP;
7487         ChessSquare toP;
7488         int frc;
7489
7490         // ignore off-board to clicks
7491         if(y < 0 || x < 0) return;
7492
7493         /* Check if clicking again on the same color piece */
7494         fromP = boards[currentMove][fromY][fromX];
7495         toP = boards[currentMove][y][x];
7496         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7497         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7498            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7499              WhitePawn <= toP && toP <= WhiteKing &&
7500              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7501              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7502             (BlackPawn <= fromP && fromP <= BlackKing &&
7503              BlackPawn <= toP && toP <= BlackKing &&
7504              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7505              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7506             /* Clicked again on same color piece -- changed his mind */
7507             second = (x == fromX && y == fromY);
7508             killX = killY = -1;
7509             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7510                 second = FALSE; // first double-click rather than scond click
7511                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7512             }
7513             promoDefaultAltered = FALSE;
7514             MarkTargetSquares(1);
7515            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7516             if (appData.highlightDragging) {
7517                 SetHighlights(x, y, -1, -1);
7518             } else {
7519                 ClearHighlights();
7520             }
7521             if (OKToStartUserMove(x, y)) {
7522                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7523                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7524                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7525                  gatingPiece = boards[currentMove][fromY][fromX];
7526                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7527                 fromX = x;
7528                 fromY = y; dragging = 1;
7529                 ReportClick("lift", x, y);
7530                 MarkTargetSquares(0);
7531                 DragPieceBegin(xPix, yPix, FALSE);
7532                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7533                     promoSweep = defaultPromoChoice;
7534                     selectFlag = 0; lastX = xPix; lastY = yPix;
7535                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7536                 }
7537             }
7538            }
7539            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7540            second = FALSE;
7541         }
7542         // ignore clicks on holdings
7543         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7544     }
7545
7546     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7547         DragPieceEnd(xPix, yPix); dragging = 0;
7548         if(clearFlag) {
7549             // a deferred attempt to click-click move an empty square on top of a piece
7550             boards[currentMove][y][x] = EmptySquare;
7551             ClearHighlights();
7552             DrawPosition(FALSE, boards[currentMove]);
7553             fromX = fromY = -1; clearFlag = 0;
7554             return;
7555         }
7556         if (appData.animateDragging) {
7557             /* Undo animation damage if any */
7558             DrawPosition(FALSE, NULL);
7559         }
7560         if (second || sweepSelecting) {
7561             /* Second up/down in same square; just abort move */
7562             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7563             second = sweepSelecting = 0;
7564             fromX = fromY = -1;
7565             gatingPiece = EmptySquare;
7566             MarkTargetSquares(1);
7567             ClearHighlights();
7568             gotPremove = 0;
7569             ClearPremoveHighlights();
7570         } else {
7571             /* First upclick in same square; start click-click mode */
7572             SetHighlights(x, y, -1, -1);
7573         }
7574         return;
7575     }
7576
7577     clearFlag = 0;
7578
7579     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7580        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7581         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7582         DisplayMessage(_("only marked squares are legal"),"");
7583         DrawPosition(TRUE, NULL);
7584         return; // ignore to-click
7585     }
7586
7587     /* we now have a different from- and (possibly off-board) to-square */
7588     /* Completed move */
7589     if(!sweepSelecting) {
7590         toX = x;
7591         toY = y;
7592     }
7593
7594     piece = boards[currentMove][fromY][fromX];
7595
7596     saveAnimate = appData.animate;
7597     if (clickType == Press) {
7598         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7599         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7600             // must be Edit Position mode with empty-square selected
7601             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7602             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7603             return;
7604         }
7605         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7606             return;
7607         }
7608         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7609             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7610         } else
7611         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7612         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7613           if(appData.sweepSelect) {
7614             promoSweep = defaultPromoChoice;
7615             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7616             selectFlag = 0; lastX = xPix; lastY = yPix;
7617             Sweep(0); // Pawn that is going to promote: preview promotion piece
7618             sweepSelecting = 1;
7619             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7620             MarkTargetSquares(1);
7621           }
7622           return; // promo popup appears on up-click
7623         }
7624         /* Finish clickclick move */
7625         if (appData.animate || appData.highlightLastMove) {
7626             SetHighlights(fromX, fromY, toX, toY);
7627         } else {
7628             ClearHighlights();
7629         }
7630     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7631         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7632         if (appData.animate || appData.highlightLastMove) {
7633             SetHighlights(fromX, fromY, toX, toY);
7634         } else {
7635             ClearHighlights();
7636         }
7637     } else {
7638 #if 0
7639 // [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
7640         /* Finish drag move */
7641         if (appData.highlightLastMove) {
7642             SetHighlights(fromX, fromY, toX, toY);
7643         } else {
7644             ClearHighlights();
7645         }
7646 #endif
7647         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7648         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7649           dragging *= 2;            // flag button-less dragging if we are dragging
7650           MarkTargetSquares(1);
7651           if(x == killX && y == killY) killX = killY = -1; else {
7652             killX = x; killY = y;     //remeber this square as intermediate
7653             ReportClick("put", x, y); // and inform engine
7654             ReportClick("lift", x, y);
7655             MarkTargetSquares(0);
7656             return;
7657           }
7658         }
7659         DragPieceEnd(xPix, yPix); dragging = 0;
7660         /* Don't animate move and drag both */
7661         appData.animate = FALSE;
7662     }
7663
7664     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7665     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7666         ChessSquare piece = boards[currentMove][fromY][fromX];
7667         if(gameMode == EditPosition && piece != EmptySquare &&
7668            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7669             int n;
7670
7671             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7672                 n = PieceToNumber(piece - (int)BlackPawn);
7673                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7674                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7675                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7676             } else
7677             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7678                 n = PieceToNumber(piece);
7679                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7680                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7681                 boards[currentMove][n][BOARD_WIDTH-2]++;
7682             }
7683             boards[currentMove][fromY][fromX] = EmptySquare;
7684         }
7685         ClearHighlights();
7686         fromX = fromY = -1;
7687         MarkTargetSquares(1);
7688         DrawPosition(TRUE, boards[currentMove]);
7689         return;
7690     }
7691
7692     // off-board moves should not be highlighted
7693     if(x < 0 || y < 0) ClearHighlights();
7694     else ReportClick("put", x, y);
7695
7696     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7697
7698     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7699         SetHighlights(fromX, fromY, toX, toY);
7700         MarkTargetSquares(1);
7701         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7702             // [HGM] super: promotion to captured piece selected from holdings
7703             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7704             promotionChoice = TRUE;
7705             // kludge follows to temporarily execute move on display, without promoting yet
7706             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7707             boards[currentMove][toY][toX] = p;
7708             DrawPosition(FALSE, boards[currentMove]);
7709             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7710             boards[currentMove][toY][toX] = q;
7711             DisplayMessage("Click in holdings to choose piece", "");
7712             return;
7713         }
7714         PromotionPopUp(promoChoice);
7715     } else {
7716         int oldMove = currentMove;
7717         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7718         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7719         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7720         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7721            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7722             DrawPosition(TRUE, boards[currentMove]);
7723         MarkTargetSquares(1);
7724         fromX = fromY = -1;
7725     }
7726     appData.animate = saveAnimate;
7727     if (appData.animate || appData.animateDragging) {
7728         /* Undo animation damage if needed */
7729         DrawPosition(FALSE, NULL);
7730     }
7731 }
7732
7733 int
7734 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7735 {   // front-end-free part taken out of PieceMenuPopup
7736     int whichMenu; int xSqr, ySqr;
7737
7738     if(seekGraphUp) { // [HGM] seekgraph
7739         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7740         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7741         return -2;
7742     }
7743
7744     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7745          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7746         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7747         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7748         if(action == Press)   {
7749             originalFlip = flipView;
7750             flipView = !flipView; // temporarily flip board to see game from partners perspective
7751             DrawPosition(TRUE, partnerBoard);
7752             DisplayMessage(partnerStatus, "");
7753             partnerUp = TRUE;
7754         } else if(action == Release) {
7755             flipView = originalFlip;
7756             DrawPosition(TRUE, boards[currentMove]);
7757             partnerUp = FALSE;
7758         }
7759         return -2;
7760     }
7761
7762     xSqr = EventToSquare(x, BOARD_WIDTH);
7763     ySqr = EventToSquare(y, BOARD_HEIGHT);
7764     if (action == Release) {
7765         if(pieceSweep != EmptySquare) {
7766             EditPositionMenuEvent(pieceSweep, toX, toY);
7767             pieceSweep = EmptySquare;
7768         } else UnLoadPV(); // [HGM] pv
7769     }
7770     if (action != Press) return -2; // return code to be ignored
7771     switch (gameMode) {
7772       case IcsExamining:
7773         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7774       case EditPosition:
7775         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7776         if (xSqr < 0 || ySqr < 0) return -1;
7777         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7778         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7779         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7780         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7781         NextPiece(0);
7782         return 2; // grab
7783       case IcsObserving:
7784         if(!appData.icsEngineAnalyze) return -1;
7785       case IcsPlayingWhite:
7786       case IcsPlayingBlack:
7787         if(!appData.zippyPlay) goto noZip;
7788       case AnalyzeMode:
7789       case AnalyzeFile:
7790       case MachinePlaysWhite:
7791       case MachinePlaysBlack:
7792       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7793         if (!appData.dropMenu) {
7794           LoadPV(x, y);
7795           return 2; // flag front-end to grab mouse events
7796         }
7797         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7798            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7799       case EditGame:
7800       noZip:
7801         if (xSqr < 0 || ySqr < 0) return -1;
7802         if (!appData.dropMenu || appData.testLegality &&
7803             gameInfo.variant != VariantBughouse &&
7804             gameInfo.variant != VariantCrazyhouse) return -1;
7805         whichMenu = 1; // drop menu
7806         break;
7807       default:
7808         return -1;
7809     }
7810
7811     if (((*fromX = xSqr) < 0) ||
7812         ((*fromY = ySqr) < 0)) {
7813         *fromX = *fromY = -1;
7814         return -1;
7815     }
7816     if (flipView)
7817       *fromX = BOARD_WIDTH - 1 - *fromX;
7818     else
7819       *fromY = BOARD_HEIGHT - 1 - *fromY;
7820
7821     return whichMenu;
7822 }
7823
7824 void
7825 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7826 {
7827 //    char * hint = lastHint;
7828     FrontEndProgramStats stats;
7829
7830     stats.which = cps == &first ? 0 : 1;
7831     stats.depth = cpstats->depth;
7832     stats.nodes = cpstats->nodes;
7833     stats.score = cpstats->score;
7834     stats.time = cpstats->time;
7835     stats.pv = cpstats->movelist;
7836     stats.hint = lastHint;
7837     stats.an_move_index = 0;
7838     stats.an_move_count = 0;
7839
7840     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7841         stats.hint = cpstats->move_name;
7842         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7843         stats.an_move_count = cpstats->nr_moves;
7844     }
7845
7846     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
7847
7848     SetProgramStats( &stats );
7849 }
7850
7851 void
7852 ClearEngineOutputPane (int which)
7853 {
7854     static FrontEndProgramStats dummyStats;
7855     dummyStats.which = which;
7856     dummyStats.pv = "#";
7857     SetProgramStats( &dummyStats );
7858 }
7859
7860 #define MAXPLAYERS 500
7861
7862 char *
7863 TourneyStandings (int display)
7864 {
7865     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7866     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7867     char result, *p, *names[MAXPLAYERS];
7868
7869     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7870         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7871     names[0] = p = strdup(appData.participants);
7872     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7873
7874     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7875
7876     while(result = appData.results[nr]) {
7877         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7878         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7879         wScore = bScore = 0;
7880         switch(result) {
7881           case '+': wScore = 2; break;
7882           case '-': bScore = 2; break;
7883           case '=': wScore = bScore = 1; break;
7884           case ' ':
7885           case '*': return strdup("busy"); // tourney not finished
7886         }
7887         score[w] += wScore;
7888         score[b] += bScore;
7889         games[w]++;
7890         games[b]++;
7891         nr++;
7892     }
7893     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7894     for(w=0; w<nPlayers; w++) {
7895         bScore = -1;
7896         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7897         ranking[w] = b; points[w] = bScore; score[b] = -2;
7898     }
7899     p = malloc(nPlayers*34+1);
7900     for(w=0; w<nPlayers && w<display; w++)
7901         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7902     free(names[0]);
7903     return p;
7904 }
7905
7906 void
7907 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7908 {       // count all piece types
7909         int p, f, r;
7910         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7911         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7912         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7913                 p = board[r][f];
7914                 pCnt[p]++;
7915                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7916                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7917                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7918                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7919                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7920                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7921         }
7922 }
7923
7924 int
7925 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7926 {
7927         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7928         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7929
7930         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7931         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7932         if(myPawns == 2 && nMine == 3) // KPP
7933             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7934         if(myPawns == 1 && nMine == 2) // KP
7935             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7936         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7937             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7938         if(myPawns) return FALSE;
7939         if(pCnt[WhiteRook+side])
7940             return pCnt[BlackRook-side] ||
7941                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7942                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7943                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7944         if(pCnt[WhiteCannon+side]) {
7945             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7946             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7947         }
7948         if(pCnt[WhiteKnight+side])
7949             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7950         return FALSE;
7951 }
7952
7953 int
7954 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7955 {
7956         VariantClass v = gameInfo.variant;
7957
7958         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7959         if(v == VariantShatranj) return TRUE; // always winnable through baring
7960         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7961         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7962
7963         if(v == VariantXiangqi) {
7964                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7965
7966                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7967                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7968                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7969                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7970                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7971                 if(stale) // we have at least one last-rank P plus perhaps C
7972                     return majors // KPKX
7973                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7974                 else // KCA*E*
7975                     return pCnt[WhiteFerz+side] // KCAK
7976                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7977                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7978                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7979
7980         } else if(v == VariantKnightmate) {
7981                 if(nMine == 1) return FALSE;
7982                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7983         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7984                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7985
7986                 if(nMine == 1) return FALSE; // bare King
7987                 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
7988                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7989                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7990                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7991                 if(pCnt[WhiteKnight+side])
7992                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7993                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7994                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7995                 if(nBishops)
7996                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7997                 if(pCnt[WhiteAlfil+side])
7998                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7999                 if(pCnt[WhiteWazir+side])
8000                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8001         }
8002
8003         return TRUE;
8004 }
8005
8006 int
8007 CompareWithRights (Board b1, Board b2)
8008 {
8009     int rights = 0;
8010     if(!CompareBoards(b1, b2)) return FALSE;
8011     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8012     /* compare castling rights */
8013     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8014            rights++; /* King lost rights, while rook still had them */
8015     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8016         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8017            rights++; /* but at least one rook lost them */
8018     }
8019     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8020            rights++;
8021     if( b1[CASTLING][5] != NoRights ) {
8022         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8023            rights++;
8024     }
8025     return rights == 0;
8026 }
8027
8028 int
8029 Adjudicate (ChessProgramState *cps)
8030 {       // [HGM] some adjudications useful with buggy engines
8031         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8032         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8033         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8034         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8035         int k, drop, count = 0; static int bare = 1;
8036         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8037         Boolean canAdjudicate = !appData.icsActive;
8038
8039         // most tests only when we understand the game, i.e. legality-checking on
8040             if( appData.testLegality )
8041             {   /* [HGM] Some more adjudications for obstinate engines */
8042                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8043                 static int moveCount = 6;
8044                 ChessMove result;
8045                 char *reason = NULL;
8046
8047                 /* Count what is on board. */
8048                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8049
8050                 /* Some material-based adjudications that have to be made before stalemate test */
8051                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8052                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8053                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8054                      if(canAdjudicate && appData.checkMates) {
8055                          if(engineOpponent)
8056                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8057                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8058                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8059                          return 1;
8060                      }
8061                 }
8062
8063                 /* Bare King in Shatranj (loses) or Losers (wins) */
8064                 if( nrW == 1 || nrB == 1) {
8065                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8066                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8067                      if(canAdjudicate && appData.checkMates) {
8068                          if(engineOpponent)
8069                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8070                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8071                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8072                          return 1;
8073                      }
8074                   } else
8075                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8076                   {    /* bare King */
8077                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8078                         if(canAdjudicate && appData.checkMates) {
8079                             /* but only adjudicate if adjudication enabled */
8080                             if(engineOpponent)
8081                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8082                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8083                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8084                             return 1;
8085                         }
8086                   }
8087                 } else bare = 1;
8088
8089
8090             // don't wait for engine to announce game end if we can judge ourselves
8091             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8092               case MT_CHECK:
8093                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8094                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8095                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8096                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8097                             checkCnt++;
8098                         if(checkCnt >= 2) {
8099                             reason = "Xboard adjudication: 3rd check";
8100                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8101                             break;
8102                         }
8103                     }
8104                 }
8105               case MT_NONE:
8106               default:
8107                 break;
8108               case MT_STEALMATE:
8109               case MT_STALEMATE:
8110               case MT_STAINMATE:
8111                 reason = "Xboard adjudication: Stalemate";
8112                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8113                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8114                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8115                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8116                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8117                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8118                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8119                                                                         EP_CHECKMATE : EP_WINS);
8120                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8121                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8122                 }
8123                 break;
8124               case MT_CHECKMATE:
8125                 reason = "Xboard adjudication: Checkmate";
8126                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8127                 if(gameInfo.variant == VariantShogi) {
8128                     if(forwardMostMove > backwardMostMove
8129                        && moveList[forwardMostMove-1][1] == '@'
8130                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8131                         reason = "XBoard adjudication: pawn-drop mate";
8132                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8133                     }
8134                 }
8135                 break;
8136             }
8137
8138                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8139                     case EP_STALEMATE:
8140                         result = GameIsDrawn; break;
8141                     case EP_CHECKMATE:
8142                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8143                     case EP_WINS:
8144                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8145                     default:
8146                         result = EndOfFile;
8147                 }
8148                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8149                     if(engineOpponent)
8150                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8151                     GameEnds( result, reason, GE_XBOARD );
8152                     return 1;
8153                 }
8154
8155                 /* Next absolutely insufficient mating material. */
8156                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8157                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8158                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8159
8160                      /* always flag draws, for judging claims */
8161                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8162
8163                      if(canAdjudicate && appData.materialDraws) {
8164                          /* but only adjudicate them if adjudication enabled */
8165                          if(engineOpponent) {
8166                            SendToProgram("force\n", engineOpponent); // suppress reply
8167                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8168                          }
8169                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8170                          return 1;
8171                      }
8172                 }
8173
8174                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8175                 if(gameInfo.variant == VariantXiangqi ?
8176                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8177                  : nrW + nrB == 4 &&
8178                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8179                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8180                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8181                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8182                    ) ) {
8183                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8184                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8185                           if(engineOpponent) {
8186                             SendToProgram("force\n", engineOpponent); // suppress reply
8187                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8188                           }
8189                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8190                           return 1;
8191                      }
8192                 } else moveCount = 6;
8193             }
8194
8195         // Repetition draws and 50-move rule can be applied independently of legality testing
8196
8197                 /* Check for rep-draws */
8198                 count = 0;
8199                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8200                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8201                 for(k = forwardMostMove-2;
8202                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8203                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8204                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8205                     k-=2)
8206                 {   int rights=0;
8207                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8208                         /* compare castling rights */
8209                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8210                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8211                                 rights++; /* King lost rights, while rook still had them */
8212                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8213                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8214                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8215                                    rights++; /* but at least one rook lost them */
8216                         }
8217                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8218                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8219                                 rights++;
8220                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8221                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8222                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8223                                    rights++;
8224                         }
8225                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8226                             && appData.drawRepeats > 1) {
8227                              /* adjudicate after user-specified nr of repeats */
8228                              int result = GameIsDrawn;
8229                              char *details = "XBoard adjudication: repetition draw";
8230                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8231                                 // [HGM] xiangqi: check for forbidden perpetuals
8232                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8233                                 for(m=forwardMostMove; m>k; m-=2) {
8234                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8235                                         ourPerpetual = 0; // the current mover did not always check
8236                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8237                                         hisPerpetual = 0; // the opponent did not always check
8238                                 }
8239                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8240                                                                         ourPerpetual, hisPerpetual);
8241                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8242                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8243                                     details = "Xboard adjudication: perpetual checking";
8244                                 } else
8245                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8246                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8247                                 } else
8248                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8249                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8250                                         result = BlackWins;
8251                                         details = "Xboard adjudication: repetition";
8252                                     }
8253                                 } else // it must be XQ
8254                                 // Now check for perpetual chases
8255                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8256                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8257                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8258                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8259                                         static char resdet[MSG_SIZ];
8260                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8261                                         details = resdet;
8262                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8263                                     } else
8264                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8265                                         break; // Abort repetition-checking loop.
8266                                 }
8267                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8268                              }
8269                              if(engineOpponent) {
8270                                SendToProgram("force\n", engineOpponent); // suppress reply
8271                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8272                              }
8273                              GameEnds( result, details, GE_XBOARD );
8274                              return 1;
8275                         }
8276                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8277                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8278                     }
8279                 }
8280
8281                 /* Now we test for 50-move draws. Determine ply count */
8282                 count = forwardMostMove;
8283                 /* look for last irreversble move */
8284                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8285                     count--;
8286                 /* if we hit starting position, add initial plies */
8287                 if( count == backwardMostMove )
8288                     count -= initialRulePlies;
8289                 count = forwardMostMove - count;
8290                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8291                         // adjust reversible move counter for checks in Xiangqi
8292                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8293                         if(i < backwardMostMove) i = backwardMostMove;
8294                         while(i <= forwardMostMove) {
8295                                 lastCheck = inCheck; // check evasion does not count
8296                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8297                                 if(inCheck || lastCheck) count--; // check does not count
8298                                 i++;
8299                         }
8300                 }
8301                 if( count >= 100)
8302                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8303                          /* this is used to judge if draw claims are legal */
8304                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8305                          if(engineOpponent) {
8306                            SendToProgram("force\n", engineOpponent); // suppress reply
8307                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8308                          }
8309                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8310                          return 1;
8311                 }
8312
8313                 /* if draw offer is pending, treat it as a draw claim
8314                  * when draw condition present, to allow engines a way to
8315                  * claim draws before making their move to avoid a race
8316                  * condition occurring after their move
8317                  */
8318                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8319                          char *p = NULL;
8320                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8321                              p = "Draw claim: 50-move rule";
8322                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8323                              p = "Draw claim: 3-fold repetition";
8324                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8325                              p = "Draw claim: insufficient mating material";
8326                          if( p != NULL && canAdjudicate) {
8327                              if(engineOpponent) {
8328                                SendToProgram("force\n", engineOpponent); // suppress reply
8329                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8330                              }
8331                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8332                              return 1;
8333                          }
8334                 }
8335
8336                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8337                     if(engineOpponent) {
8338                       SendToProgram("force\n", engineOpponent); // suppress reply
8339                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                     }
8341                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8342                     return 1;
8343                 }
8344         return 0;
8345 }
8346
8347 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8348 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8349 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8350
8351 static int
8352 BitbaseProbe ()
8353 {
8354     int pieces[10], squares[10], cnt=0, r, f, res;
8355     static int loaded;
8356     static PPROBE_EGBB probeBB;
8357     if(!appData.testLegality) return 10;
8358     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8359     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8360     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8361     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8362         ChessSquare piece = boards[forwardMostMove][r][f];
8363         int black = (piece >= BlackPawn);
8364         int type = piece - black*BlackPawn;
8365         if(piece == EmptySquare) continue;
8366         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8367         if(type == WhiteKing) type = WhiteQueen + 1;
8368         type = egbbCode[type];
8369         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8370         pieces[cnt] = type + black*6;
8371         if(++cnt > 5) return 11;
8372     }
8373     pieces[cnt] = squares[cnt] = 0;
8374     // probe EGBB
8375     if(loaded == 2) return 13; // loading failed before
8376     if(loaded == 0) {
8377         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8378         HMODULE lib;
8379         PLOAD_EGBB loadBB;
8380         loaded = 2; // prepare for failure
8381         if(!path) return 13; // no egbb installed
8382         strncpy(buf, path + 8, MSG_SIZ);
8383         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8384         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8385         lib = LoadLibrary(buf);
8386         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8387         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8388         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8389         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8390         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8391         loaded = 1; // success!
8392     }
8393     res = probeBB(forwardMostMove & 1, pieces, squares);
8394     return res > 0 ? 1 : res < 0 ? -1 : 0;
8395 }
8396
8397 char *
8398 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8399 {   // [HGM] book: this routine intercepts moves to simulate book replies
8400     char *bookHit = NULL;
8401
8402     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8403         char buf[MSG_SIZ];
8404         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8405         SendToProgram(buf, cps);
8406     }
8407     //first determine if the incoming move brings opponent into his book
8408     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8409         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8410     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8411     if(bookHit != NULL && !cps->bookSuspend) {
8412         // make sure opponent is not going to reply after receiving move to book position
8413         SendToProgram("force\n", cps);
8414         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8415     }
8416     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8417     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8418     // now arrange restart after book miss
8419     if(bookHit) {
8420         // after a book hit we never send 'go', and the code after the call to this routine
8421         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8422         char buf[MSG_SIZ], *move = bookHit;
8423         if(cps->useSAN) {
8424             int fromX, fromY, toX, toY;
8425             char promoChar;
8426             ChessMove moveType;
8427             move = buf + 30;
8428             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8429                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8430                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8431                                     PosFlags(forwardMostMove),
8432                                     fromY, fromX, toY, toX, promoChar, move);
8433             } else {
8434                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8435                 bookHit = NULL;
8436             }
8437         }
8438         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8439         SendToProgram(buf, cps);
8440         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8441     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8442         SendToProgram("go\n", cps);
8443         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8444     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8445         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8446             SendToProgram("go\n", cps);
8447         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8448     }
8449     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8450 }
8451
8452 int
8453 LoadError (char *errmess, ChessProgramState *cps)
8454 {   // unloads engine and switches back to -ncp mode if it was first
8455     if(cps->initDone) return FALSE;
8456     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8457     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8458     cps->pr = NoProc;
8459     if(cps == &first) {
8460         appData.noChessProgram = TRUE;
8461         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8462         gameMode = BeginningOfGame; ModeHighlight();
8463         SetNCPMode();
8464     }
8465     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8466     DisplayMessage("", ""); // erase waiting message
8467     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8468     return TRUE;
8469 }
8470
8471 char *savedMessage;
8472 ChessProgramState *savedState;
8473 void
8474 DeferredBookMove (void)
8475 {
8476         if(savedState->lastPing != savedState->lastPong)
8477                     ScheduleDelayedEvent(DeferredBookMove, 10);
8478         else
8479         HandleMachineMove(savedMessage, savedState);
8480 }
8481
8482 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8483 static ChessProgramState *stalledEngine;
8484 static char stashedInputMove[MSG_SIZ];
8485
8486 void
8487 HandleMachineMove (char *message, ChessProgramState *cps)
8488 {
8489     static char firstLeg[20];
8490     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8491     char realname[MSG_SIZ];
8492     int fromX, fromY, toX, toY;
8493     ChessMove moveType;
8494     char promoChar, roar;
8495     char *p, *pv=buf1;
8496     int machineWhite, oldError;
8497     char *bookHit;
8498
8499     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8500         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8501         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8502             DisplayError(_("Invalid pairing from pairing engine"), 0);
8503             return;
8504         }
8505         pairingReceived = 1;
8506         NextMatchGame();
8507         return; // Skim the pairing messages here.
8508     }
8509
8510     oldError = cps->userError; cps->userError = 0;
8511
8512 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8513     /*
8514      * Kludge to ignore BEL characters
8515      */
8516     while (*message == '\007') message++;
8517
8518     /*
8519      * [HGM] engine debug message: ignore lines starting with '#' character
8520      */
8521     if(cps->debug && *message == '#') return;
8522
8523     /*
8524      * Look for book output
8525      */
8526     if (cps == &first && bookRequested) {
8527         if (message[0] == '\t' || message[0] == ' ') {
8528             /* Part of the book output is here; append it */
8529             strcat(bookOutput, message);
8530             strcat(bookOutput, "  \n");
8531             return;
8532         } else if (bookOutput[0] != NULLCHAR) {
8533             /* All of book output has arrived; display it */
8534             char *p = bookOutput;
8535             while (*p != NULLCHAR) {
8536                 if (*p == '\t') *p = ' ';
8537                 p++;
8538             }
8539             DisplayInformation(bookOutput);
8540             bookRequested = FALSE;
8541             /* Fall through to parse the current output */
8542         }
8543     }
8544
8545     /*
8546      * Look for machine move.
8547      */
8548     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8549         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8550     {
8551         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8552             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8553             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8554             stalledEngine = cps;
8555             if(appData.ponderNextMove) { // bring opponent out of ponder
8556                 if(gameMode == TwoMachinesPlay) {
8557                     if(cps->other->pause)
8558                         PauseEngine(cps->other);
8559                     else
8560                         SendToProgram("easy\n", cps->other);
8561                 }
8562             }
8563             StopClocks();
8564             return;
8565         }
8566
8567         /* This method is only useful on engines that support ping */
8568         if (cps->lastPing != cps->lastPong) {
8569           if (gameMode == BeginningOfGame) {
8570             /* Extra move from before last new; ignore */
8571             if (appData.debugMode) {
8572                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8573             }
8574           } else {
8575             if (appData.debugMode) {
8576                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8577                         cps->which, gameMode);
8578             }
8579
8580             SendToProgram("undo\n", cps);
8581           }
8582           return;
8583         }
8584
8585         switch (gameMode) {
8586           case BeginningOfGame:
8587             /* Extra move from before last reset; ignore */
8588             if (appData.debugMode) {
8589                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8590             }
8591             return;
8592
8593           case EndOfGame:
8594           case IcsIdle:
8595           default:
8596             /* Extra move after we tried to stop.  The mode test is
8597                not a reliable way of detecting this problem, but it's
8598                the best we can do on engines that don't support ping.
8599             */
8600             if (appData.debugMode) {
8601                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8602                         cps->which, gameMode);
8603             }
8604             SendToProgram("undo\n", cps);
8605             return;
8606
8607           case MachinePlaysWhite:
8608           case IcsPlayingWhite:
8609             machineWhite = TRUE;
8610             break;
8611
8612           case MachinePlaysBlack:
8613           case IcsPlayingBlack:
8614             machineWhite = FALSE;
8615             break;
8616
8617           case TwoMachinesPlay:
8618             machineWhite = (cps->twoMachinesColor[0] == 'w');
8619             break;
8620         }
8621         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8622             if (appData.debugMode) {
8623                 fprintf(debugFP,
8624                         "Ignoring move out of turn by %s, gameMode %d"
8625                         ", forwardMost %d\n",
8626                         cps->which, gameMode, forwardMostMove);
8627             }
8628             return;
8629         }
8630
8631         if(cps->alphaRank) AlphaRank(machineMove, 4);
8632
8633         // [HGM] lion: (some very limited) support for Alien protocol
8634         killX = killY = -1;
8635         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8636             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8637             return;
8638         } else if(firstLeg[0]) { // there was a previous leg;
8639             // only support case where same piece makes two step (and don't even test that!)
8640             char buf[20], *p = machineMove+1, *q = buf+1, f;
8641             safeStrCpy(buf, machineMove, 20);
8642             while(isdigit(*q)) q++; // find start of to-square
8643             safeStrCpy(machineMove, firstLeg, 20);
8644             while(isdigit(*p)) p++;
8645             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8646             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8647             firstLeg[0] = NULLCHAR;
8648         }
8649
8650         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8651                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8652             /* Machine move could not be parsed; ignore it. */
8653           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8654                     machineMove, _(cps->which));
8655             DisplayMoveError(buf1);
8656             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8657                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8658             if (gameMode == TwoMachinesPlay) {
8659               GameEnds(machineWhite ? BlackWins : WhiteWins,
8660                        buf1, GE_XBOARD);
8661             }
8662             return;
8663         }
8664
8665         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8666         /* So we have to redo legality test with true e.p. status here,  */
8667         /* to make sure an illegal e.p. capture does not slip through,   */
8668         /* to cause a forfeit on a justified illegal-move complaint      */
8669         /* of the opponent.                                              */
8670         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8671            ChessMove moveType;
8672            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8673                              fromY, fromX, toY, toX, promoChar);
8674             if(moveType == IllegalMove) {
8675               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8676                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8677                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8678                            buf1, GE_XBOARD);
8679                 return;
8680            } else if(!appData.fischerCastling)
8681            /* [HGM] Kludge to handle engines that send FRC-style castling
8682               when they shouldn't (like TSCP-Gothic) */
8683            switch(moveType) {
8684              case WhiteASideCastleFR:
8685              case BlackASideCastleFR:
8686                toX+=2;
8687                currentMoveString[2]++;
8688                break;
8689              case WhiteHSideCastleFR:
8690              case BlackHSideCastleFR:
8691                toX--;
8692                currentMoveString[2]--;
8693                break;
8694              default: ; // nothing to do, but suppresses warning of pedantic compilers
8695            }
8696         }
8697         hintRequested = FALSE;
8698         lastHint[0] = NULLCHAR;
8699         bookRequested = FALSE;
8700         /* Program may be pondering now */
8701         cps->maybeThinking = TRUE;
8702         if (cps->sendTime == 2) cps->sendTime = 1;
8703         if (cps->offeredDraw) cps->offeredDraw--;
8704
8705         /* [AS] Save move info*/
8706         pvInfoList[ forwardMostMove ].score = programStats.score;
8707         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8708         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8709
8710         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8711
8712         /* Test suites abort the 'game' after one move */
8713         if(*appData.finger) {
8714            static FILE *f;
8715            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8716            if(!f) f = fopen(appData.finger, "w");
8717            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8718            else { DisplayFatalError("Bad output file", errno, 0); return; }
8719            free(fen);
8720            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8721         }
8722
8723         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8724         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8725             int count = 0;
8726
8727             while( count < adjudicateLossPlies ) {
8728                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8729
8730                 if( count & 1 ) {
8731                     score = -score; /* Flip score for winning side */
8732                 }
8733
8734                 if( score > appData.adjudicateLossThreshold ) {
8735                     break;
8736                 }
8737
8738                 count++;
8739             }
8740
8741             if( count >= adjudicateLossPlies ) {
8742                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8743
8744                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8745                     "Xboard adjudication",
8746                     GE_XBOARD );
8747
8748                 return;
8749             }
8750         }
8751
8752         if(Adjudicate(cps)) {
8753             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8754             return; // [HGM] adjudicate: for all automatic game ends
8755         }
8756
8757 #if ZIPPY
8758         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8759             first.initDone) {
8760           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8761                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8762                 SendToICS("draw ");
8763                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8764           }
8765           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8766           ics_user_moved = 1;
8767           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8768                 char buf[3*MSG_SIZ];
8769
8770                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8771                         programStats.score / 100.,
8772                         programStats.depth,
8773                         programStats.time / 100.,
8774                         (unsigned int)programStats.nodes,
8775                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8776                         programStats.movelist);
8777                 SendToICS(buf);
8778           }
8779         }
8780 #endif
8781
8782         /* [AS] Clear stats for next move */
8783         ClearProgramStats();
8784         thinkOutput[0] = NULLCHAR;
8785         hiddenThinkOutputState = 0;
8786
8787         bookHit = NULL;
8788         if (gameMode == TwoMachinesPlay) {
8789             /* [HGM] relaying draw offers moved to after reception of move */
8790             /* and interpreting offer as claim if it brings draw condition */
8791             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8792                 SendToProgram("draw\n", cps->other);
8793             }
8794             if (cps->other->sendTime) {
8795                 SendTimeRemaining(cps->other,
8796                                   cps->other->twoMachinesColor[0] == 'w');
8797             }
8798             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8799             if (firstMove && !bookHit) {
8800                 firstMove = FALSE;
8801                 if (cps->other->useColors) {
8802                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8803                 }
8804                 SendToProgram("go\n", cps->other);
8805             }
8806             cps->other->maybeThinking = TRUE;
8807         }
8808
8809         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8810
8811         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8812
8813         if (!pausing && appData.ringBellAfterMoves) {
8814             if(!roar) RingBell();
8815         }
8816
8817         /*
8818          * Reenable menu items that were disabled while
8819          * machine was thinking
8820          */
8821         if (gameMode != TwoMachinesPlay)
8822             SetUserThinkingEnables();
8823
8824         // [HGM] book: after book hit opponent has received move and is now in force mode
8825         // force the book reply into it, and then fake that it outputted this move by jumping
8826         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8827         if(bookHit) {
8828                 static char bookMove[MSG_SIZ]; // a bit generous?
8829
8830                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8831                 strcat(bookMove, bookHit);
8832                 message = bookMove;
8833                 cps = cps->other;
8834                 programStats.nodes = programStats.depth = programStats.time =
8835                 programStats.score = programStats.got_only_move = 0;
8836                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8837
8838                 if(cps->lastPing != cps->lastPong) {
8839                     savedMessage = message; // args for deferred call
8840                     savedState = cps;
8841                     ScheduleDelayedEvent(DeferredBookMove, 10);
8842                     return;
8843                 }
8844                 goto FakeBookMove;
8845         }
8846
8847         return;
8848     }
8849
8850     /* Set special modes for chess engines.  Later something general
8851      *  could be added here; for now there is just one kludge feature,
8852      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8853      *  when "xboard" is given as an interactive command.
8854      */
8855     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8856         cps->useSigint = FALSE;
8857         cps->useSigterm = FALSE;
8858     }
8859     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8860       ParseFeatures(message+8, cps);
8861       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8862     }
8863
8864     if (!strncmp(message, "setup ", 6) && 
8865         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8866           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8867                                         ) { // [HGM] allow first engine to define opening position
8868       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8869       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8870       *buf = NULLCHAR;
8871       if(sscanf(message, "setup (%s", buf) == 1) {
8872         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8873         ASSIGN(appData.pieceToCharTable, buf);
8874       }
8875       if(startedFromSetupPosition) return;
8876       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8877       if(dummy >= 3) {
8878         while(message[s] && message[s++] != ' ');
8879         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8880            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8881             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8882             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8883           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8884           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8885         }
8886       }
8887       ParseFEN(boards[0], &dummy, message+s, FALSE);
8888       DrawPosition(TRUE, boards[0]);
8889       startedFromSetupPosition = TRUE;
8890       return;
8891     }
8892     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8893       ChessSquare piece = WhitePawn;
8894       char *p=buf2;
8895       if(cps != &first || appData.testLegality) return;
8896       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8897       piece += CharToPiece(*p) - WhitePawn;
8898       if(piece < EmptySquare) {
8899         pieceDefs = TRUE;
8900         ASSIGN(pieceDesc[piece], buf1);
8901         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8902       }
8903       return;
8904     }
8905     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8906      * want this, I was asked to put it in, and obliged.
8907      */
8908     if (!strncmp(message, "setboard ", 9)) {
8909         Board initial_position;
8910
8911         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8912
8913         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8914             DisplayError(_("Bad FEN received from engine"), 0);
8915             return ;
8916         } else {
8917            Reset(TRUE, FALSE);
8918            CopyBoard(boards[0], initial_position);
8919            initialRulePlies = FENrulePlies;
8920            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8921            else gameMode = MachinePlaysBlack;
8922            DrawPosition(FALSE, boards[currentMove]);
8923         }
8924         return;
8925     }
8926
8927     /*
8928      * Look for communication commands
8929      */
8930     if (!strncmp(message, "telluser ", 9)) {
8931         if(message[9] == '\\' && message[10] == '\\')
8932             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8933         PlayTellSound();
8934         DisplayNote(message + 9);
8935         return;
8936     }
8937     if (!strncmp(message, "tellusererror ", 14)) {
8938         cps->userError = 1;
8939         if(message[14] == '\\' && message[15] == '\\')
8940             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8941         PlayTellSound();
8942         DisplayError(message + 14, 0);
8943         return;
8944     }
8945     if (!strncmp(message, "tellopponent ", 13)) {
8946       if (appData.icsActive) {
8947         if (loggedOn) {
8948           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8949           SendToICS(buf1);
8950         }
8951       } else {
8952         DisplayNote(message + 13);
8953       }
8954       return;
8955     }
8956     if (!strncmp(message, "tellothers ", 11)) {
8957       if (appData.icsActive) {
8958         if (loggedOn) {
8959           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8960           SendToICS(buf1);
8961         }
8962       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8963       return;
8964     }
8965     if (!strncmp(message, "tellall ", 8)) {
8966       if (appData.icsActive) {
8967         if (loggedOn) {
8968           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8969           SendToICS(buf1);
8970         }
8971       } else {
8972         DisplayNote(message + 8);
8973       }
8974       return;
8975     }
8976     if (strncmp(message, "warning", 7) == 0) {
8977         /* Undocumented feature, use tellusererror in new code */
8978         DisplayError(message, 0);
8979         return;
8980     }
8981     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8982         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8983         strcat(realname, " query");
8984         AskQuestion(realname, buf2, buf1, cps->pr);
8985         return;
8986     }
8987     /* Commands from the engine directly to ICS.  We don't allow these to be
8988      *  sent until we are logged on. Crafty kibitzes have been known to
8989      *  interfere with the login process.
8990      */
8991     if (loggedOn) {
8992         if (!strncmp(message, "tellics ", 8)) {
8993             SendToICS(message + 8);
8994             SendToICS("\n");
8995             return;
8996         }
8997         if (!strncmp(message, "tellicsnoalias ", 15)) {
8998             SendToICS(ics_prefix);
8999             SendToICS(message + 15);
9000             SendToICS("\n");
9001             return;
9002         }
9003         /* The following are for backward compatibility only */
9004         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9005             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9006             SendToICS(ics_prefix);
9007             SendToICS(message);
9008             SendToICS("\n");
9009             return;
9010         }
9011     }
9012     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9013         if(initPing == cps->lastPong) {
9014             if(gameInfo.variant == VariantUnknown) {
9015                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9016                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9017                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9018             }
9019             initPing = -1;
9020         }
9021         return;
9022     }
9023     if(!strncmp(message, "highlight ", 10)) {
9024         if(appData.testLegality && appData.markers) return;
9025         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9026         return;
9027     }
9028     if(!strncmp(message, "click ", 6)) {
9029         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9030         if(appData.testLegality || !appData.oneClick) return;
9031         sscanf(message+6, "%c%d%c", &f, &y, &c);
9032         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9033         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9034         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9035         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9036         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9037         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9038             LeftClick(Release, lastLeftX, lastLeftY);
9039         controlKey  = (c == ',');
9040         LeftClick(Press, x, y);
9041         LeftClick(Release, x, y);
9042         first.highlight = f;
9043         return;
9044     }
9045     /*
9046      * If the move is illegal, cancel it and redraw the board.
9047      * Also deal with other error cases.  Matching is rather loose
9048      * here to accommodate engines written before the spec.
9049      */
9050     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9051         strncmp(message, "Error", 5) == 0) {
9052         if (StrStr(message, "name") ||
9053             StrStr(message, "rating") || StrStr(message, "?") ||
9054             StrStr(message, "result") || StrStr(message, "board") ||
9055             StrStr(message, "bk") || StrStr(message, "computer") ||
9056             StrStr(message, "variant") || StrStr(message, "hint") ||
9057             StrStr(message, "random") || StrStr(message, "depth") ||
9058             StrStr(message, "accepted")) {
9059             return;
9060         }
9061         if (StrStr(message, "protover")) {
9062           /* Program is responding to input, so it's apparently done
9063              initializing, and this error message indicates it is
9064              protocol version 1.  So we don't need to wait any longer
9065              for it to initialize and send feature commands. */
9066           FeatureDone(cps, 1);
9067           cps->protocolVersion = 1;
9068           return;
9069         }
9070         cps->maybeThinking = FALSE;
9071
9072         if (StrStr(message, "draw")) {
9073             /* Program doesn't have "draw" command */
9074             cps->sendDrawOffers = 0;
9075             return;
9076         }
9077         if (cps->sendTime != 1 &&
9078             (StrStr(message, "time") || StrStr(message, "otim"))) {
9079           /* Program apparently doesn't have "time" or "otim" command */
9080           cps->sendTime = 0;
9081           return;
9082         }
9083         if (StrStr(message, "analyze")) {
9084             cps->analysisSupport = FALSE;
9085             cps->analyzing = FALSE;
9086 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9087             EditGameEvent(); // [HGM] try to preserve loaded game
9088             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9089             DisplayError(buf2, 0);
9090             return;
9091         }
9092         if (StrStr(message, "(no matching move)st")) {
9093           /* Special kludge for GNU Chess 4 only */
9094           cps->stKludge = TRUE;
9095           SendTimeControl(cps, movesPerSession, timeControl,
9096                           timeIncrement, appData.searchDepth,
9097                           searchTime);
9098           return;
9099         }
9100         if (StrStr(message, "(no matching move)sd")) {
9101           /* Special kludge for GNU Chess 4 only */
9102           cps->sdKludge = TRUE;
9103           SendTimeControl(cps, movesPerSession, timeControl,
9104                           timeIncrement, appData.searchDepth,
9105                           searchTime);
9106           return;
9107         }
9108         if (!StrStr(message, "llegal")) {
9109             return;
9110         }
9111         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9112             gameMode == IcsIdle) return;
9113         if (forwardMostMove <= backwardMostMove) return;
9114         if (pausing) PauseEvent();
9115       if(appData.forceIllegal) {
9116             // [HGM] illegal: machine refused move; force position after move into it
9117           SendToProgram("force\n", cps);
9118           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9119                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9120                 // when black is to move, while there might be nothing on a2 or black
9121                 // might already have the move. So send the board as if white has the move.
9122                 // But first we must change the stm of the engine, as it refused the last move
9123                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9124                 if(WhiteOnMove(forwardMostMove)) {
9125                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9126                     SendBoard(cps, forwardMostMove); // kludgeless board
9127                 } else {
9128                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9129                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9130                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9131                 }
9132           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9133             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9134                  gameMode == TwoMachinesPlay)
9135               SendToProgram("go\n", cps);
9136             return;
9137       } else
9138         if (gameMode == PlayFromGameFile) {
9139             /* Stop reading this game file */
9140             gameMode = EditGame;
9141             ModeHighlight();
9142         }
9143         /* [HGM] illegal-move claim should forfeit game when Xboard */
9144         /* only passes fully legal moves                            */
9145         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9146             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9147                                 "False illegal-move claim", GE_XBOARD );
9148             return; // do not take back move we tested as valid
9149         }
9150         currentMove = forwardMostMove-1;
9151         DisplayMove(currentMove-1); /* before DisplayMoveError */
9152         SwitchClocks(forwardMostMove-1); // [HGM] race
9153         DisplayBothClocks();
9154         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9155                 parseList[currentMove], _(cps->which));
9156         DisplayMoveError(buf1);
9157         DrawPosition(FALSE, boards[currentMove]);
9158
9159         SetUserThinkingEnables();
9160         return;
9161     }
9162     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9163         /* Program has a broken "time" command that
9164            outputs a string not ending in newline.
9165            Don't use it. */
9166         cps->sendTime = 0;
9167     }
9168     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9169         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9170             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9171     }
9172
9173     /*
9174      * If chess program startup fails, exit with an error message.
9175      * Attempts to recover here are futile. [HGM] Well, we try anyway
9176      */
9177     if ((StrStr(message, "unknown host") != NULL)
9178         || (StrStr(message, "No remote directory") != NULL)
9179         || (StrStr(message, "not found") != NULL)
9180         || (StrStr(message, "No such file") != NULL)
9181         || (StrStr(message, "can't alloc") != NULL)
9182         || (StrStr(message, "Permission denied") != NULL)) {
9183
9184         cps->maybeThinking = FALSE;
9185         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9186                 _(cps->which), cps->program, cps->host, message);
9187         RemoveInputSource(cps->isr);
9188         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9189             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9190             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9191         }
9192         return;
9193     }
9194
9195     /*
9196      * Look for hint output
9197      */
9198     if (sscanf(message, "Hint: %s", buf1) == 1) {
9199         if (cps == &first && hintRequested) {
9200             hintRequested = FALSE;
9201             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9202                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9203                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9204                                     PosFlags(forwardMostMove),
9205                                     fromY, fromX, toY, toX, promoChar, buf1);
9206                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9207                 DisplayInformation(buf2);
9208             } else {
9209                 /* Hint move could not be parsed!? */
9210               snprintf(buf2, sizeof(buf2),
9211                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9212                         buf1, _(cps->which));
9213                 DisplayError(buf2, 0);
9214             }
9215         } else {
9216           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9217         }
9218         return;
9219     }
9220
9221     /*
9222      * Ignore other messages if game is not in progress
9223      */
9224     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9225         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9226
9227     /*
9228      * look for win, lose, draw, or draw offer
9229      */
9230     if (strncmp(message, "1-0", 3) == 0) {
9231         char *p, *q, *r = "";
9232         p = strchr(message, '{');
9233         if (p) {
9234             q = strchr(p, '}');
9235             if (q) {
9236                 *q = NULLCHAR;
9237                 r = p + 1;
9238             }
9239         }
9240         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9241         return;
9242     } else if (strncmp(message, "0-1", 3) == 0) {
9243         char *p, *q, *r = "";
9244         p = strchr(message, '{');
9245         if (p) {
9246             q = strchr(p, '}');
9247             if (q) {
9248                 *q = NULLCHAR;
9249                 r = p + 1;
9250             }
9251         }
9252         /* Kludge for Arasan 4.1 bug */
9253         if (strcmp(r, "Black resigns") == 0) {
9254             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9255             return;
9256         }
9257         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9258         return;
9259     } else if (strncmp(message, "1/2", 3) == 0) {
9260         char *p, *q, *r = "";
9261         p = strchr(message, '{');
9262         if (p) {
9263             q = strchr(p, '}');
9264             if (q) {
9265                 *q = NULLCHAR;
9266                 r = p + 1;
9267             }
9268         }
9269
9270         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9271         return;
9272
9273     } else if (strncmp(message, "White resign", 12) == 0) {
9274         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strncmp(message, "Black resign", 12) == 0) {
9277         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9278         return;
9279     } else if (strncmp(message, "White matches", 13) == 0 ||
9280                strncmp(message, "Black matches", 13) == 0   ) {
9281         /* [HGM] ignore GNUShogi noises */
9282         return;
9283     } else if (strncmp(message, "White", 5) == 0 &&
9284                message[5] != '(' &&
9285                StrStr(message, "Black") == NULL) {
9286         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9287         return;
9288     } else if (strncmp(message, "Black", 5) == 0 &&
9289                message[5] != '(') {
9290         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9291         return;
9292     } else if (strcmp(message, "resign") == 0 ||
9293                strcmp(message, "computer resigns") == 0) {
9294         switch (gameMode) {
9295           case MachinePlaysBlack:
9296           case IcsPlayingBlack:
9297             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9298             break;
9299           case MachinePlaysWhite:
9300           case IcsPlayingWhite:
9301             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9302             break;
9303           case TwoMachinesPlay:
9304             if (cps->twoMachinesColor[0] == 'w')
9305               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9306             else
9307               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9308             break;
9309           default:
9310             /* can't happen */
9311             break;
9312         }
9313         return;
9314     } else if (strncmp(message, "opponent mates", 14) == 0) {
9315         switch (gameMode) {
9316           case MachinePlaysBlack:
9317           case IcsPlayingBlack:
9318             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9319             break;
9320           case MachinePlaysWhite:
9321           case IcsPlayingWhite:
9322             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9323             break;
9324           case TwoMachinesPlay:
9325             if (cps->twoMachinesColor[0] == 'w')
9326               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9327             else
9328               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9329             break;
9330           default:
9331             /* can't happen */
9332             break;
9333         }
9334         return;
9335     } else if (strncmp(message, "computer mates", 14) == 0) {
9336         switch (gameMode) {
9337           case MachinePlaysBlack:
9338           case IcsPlayingBlack:
9339             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9340             break;
9341           case MachinePlaysWhite:
9342           case IcsPlayingWhite:
9343             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9344             break;
9345           case TwoMachinesPlay:
9346             if (cps->twoMachinesColor[0] == 'w')
9347               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9348             else
9349               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9350             break;
9351           default:
9352             /* can't happen */
9353             break;
9354         }
9355         return;
9356     } else if (strncmp(message, "checkmate", 9) == 0) {
9357         if (WhiteOnMove(forwardMostMove)) {
9358             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9359         } else {
9360             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9361         }
9362         return;
9363     } else if (strstr(message, "Draw") != NULL ||
9364                strstr(message, "game is a draw") != NULL) {
9365         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9366         return;
9367     } else if (strstr(message, "offer") != NULL &&
9368                strstr(message, "draw") != NULL) {
9369 #if ZIPPY
9370         if (appData.zippyPlay && first.initDone) {
9371             /* Relay offer to ICS */
9372             SendToICS(ics_prefix);
9373             SendToICS("draw\n");
9374         }
9375 #endif
9376         cps->offeredDraw = 2; /* valid until this engine moves twice */
9377         if (gameMode == TwoMachinesPlay) {
9378             if (cps->other->offeredDraw) {
9379                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9380             /* [HGM] in two-machine mode we delay relaying draw offer      */
9381             /* until after we also have move, to see if it is really claim */
9382             }
9383         } else if (gameMode == MachinePlaysWhite ||
9384                    gameMode == MachinePlaysBlack) {
9385           if (userOfferedDraw) {
9386             DisplayInformation(_("Machine accepts your draw offer"));
9387             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9388           } else {
9389             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9390           }
9391         }
9392     }
9393
9394
9395     /*
9396      * Look for thinking output
9397      */
9398     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9399           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9400                                 ) {
9401         int plylev, mvleft, mvtot, curscore, time;
9402         char mvname[MOVE_LEN];
9403         u64 nodes; // [DM]
9404         char plyext;
9405         int ignore = FALSE;
9406         int prefixHint = FALSE;
9407         mvname[0] = NULLCHAR;
9408
9409         switch (gameMode) {
9410           case MachinePlaysBlack:
9411           case IcsPlayingBlack:
9412             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9413             break;
9414           case MachinePlaysWhite:
9415           case IcsPlayingWhite:
9416             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9417             break;
9418           case AnalyzeMode:
9419           case AnalyzeFile:
9420             break;
9421           case IcsObserving: /* [DM] icsEngineAnalyze */
9422             if (!appData.icsEngineAnalyze) ignore = TRUE;
9423             break;
9424           case TwoMachinesPlay:
9425             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9426                 ignore = TRUE;
9427             }
9428             break;
9429           default:
9430             ignore = TRUE;
9431             break;
9432         }
9433
9434         if (!ignore) {
9435             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9436             buf1[0] = NULLCHAR;
9437             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9438                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9439
9440                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9441                     nodes += u64Const(0x100000000);
9442
9443                 if (plyext != ' ' && plyext != '\t') {
9444                     time *= 100;
9445                 }
9446
9447                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9448                 if( cps->scoreIsAbsolute &&
9449                     ( gameMode == MachinePlaysBlack ||
9450                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9451                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9452                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9453                      !WhiteOnMove(currentMove)
9454                     ) )
9455                 {
9456                     curscore = -curscore;
9457                 }
9458
9459                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9460
9461                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9462                         char buf[MSG_SIZ];
9463                         FILE *f;
9464                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9465                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9466                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9467                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9468                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9469                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9470                                 fclose(f);
9471                         }
9472                         else
9473                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9474                           DisplayError(_("failed writing PV"), 0);
9475                 }
9476
9477                 tempStats.depth = plylev;
9478                 tempStats.nodes = nodes;
9479                 tempStats.time = time;
9480                 tempStats.score = curscore;
9481                 tempStats.got_only_move = 0;
9482
9483                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9484                         int ticklen;
9485
9486                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9487                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9488                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9489                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9490                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9491                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9492                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9493                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9494                 }
9495
9496                 /* Buffer overflow protection */
9497                 if (pv[0] != NULLCHAR) {
9498                     if (strlen(pv) >= sizeof(tempStats.movelist)
9499                         && appData.debugMode) {
9500                         fprintf(debugFP,
9501                                 "PV is too long; using the first %u bytes.\n",
9502                                 (unsigned) sizeof(tempStats.movelist) - 1);
9503                     }
9504
9505                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9506                 } else {
9507                     sprintf(tempStats.movelist, " no PV\n");
9508                 }
9509
9510                 if (tempStats.seen_stat) {
9511                     tempStats.ok_to_send = 1;
9512                 }
9513
9514                 if (strchr(tempStats.movelist, '(') != NULL) {
9515                     tempStats.line_is_book = 1;
9516                     tempStats.nr_moves = 0;
9517                     tempStats.moves_left = 0;
9518                 } else {
9519                     tempStats.line_is_book = 0;
9520                 }
9521
9522                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9523                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9524
9525                 SendProgramStatsToFrontend( cps, &tempStats );
9526
9527                 /*
9528                     [AS] Protect the thinkOutput buffer from overflow... this
9529                     is only useful if buf1 hasn't overflowed first!
9530                 */
9531                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9532                          plylev,
9533                          (gameMode == TwoMachinesPlay ?
9534                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9535                          ((double) curscore) / 100.0,
9536                          prefixHint ? lastHint : "",
9537                          prefixHint ? " " : "" );
9538
9539                 if( buf1[0] != NULLCHAR ) {
9540                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9541
9542                     if( strlen(pv) > max_len ) {
9543                         if( appData.debugMode) {
9544                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9545                         }
9546                         pv[max_len+1] = '\0';
9547                     }
9548
9549                     strcat( thinkOutput, pv);
9550                 }
9551
9552                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9553                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9554                     DisplayMove(currentMove - 1);
9555                 }
9556                 return;
9557
9558             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9559                 /* crafty (9.25+) says "(only move) <move>"
9560                  * if there is only 1 legal move
9561                  */
9562                 sscanf(p, "(only move) %s", buf1);
9563                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9564                 sprintf(programStats.movelist, "%s (only move)", buf1);
9565                 programStats.depth = 1;
9566                 programStats.nr_moves = 1;
9567                 programStats.moves_left = 1;
9568                 programStats.nodes = 1;
9569                 programStats.time = 1;
9570                 programStats.got_only_move = 1;
9571
9572                 /* Not really, but we also use this member to
9573                    mean "line isn't going to change" (Crafty
9574                    isn't searching, so stats won't change) */
9575                 programStats.line_is_book = 1;
9576
9577                 SendProgramStatsToFrontend( cps, &programStats );
9578
9579                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9580                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9581                     DisplayMove(currentMove - 1);
9582                 }
9583                 return;
9584             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9585                               &time, &nodes, &plylev, &mvleft,
9586                               &mvtot, mvname) >= 5) {
9587                 /* The stat01: line is from Crafty (9.29+) in response
9588                    to the "." command */
9589                 programStats.seen_stat = 1;
9590                 cps->maybeThinking = TRUE;
9591
9592                 if (programStats.got_only_move || !appData.periodicUpdates)
9593                   return;
9594
9595                 programStats.depth = plylev;
9596                 programStats.time = time;
9597                 programStats.nodes = nodes;
9598                 programStats.moves_left = mvleft;
9599                 programStats.nr_moves = mvtot;
9600                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9601                 programStats.ok_to_send = 1;
9602                 programStats.movelist[0] = '\0';
9603
9604                 SendProgramStatsToFrontend( cps, &programStats );
9605
9606                 return;
9607
9608             } else if (strncmp(message,"++",2) == 0) {
9609                 /* Crafty 9.29+ outputs this */
9610                 programStats.got_fail = 2;
9611                 return;
9612
9613             } else if (strncmp(message,"--",2) == 0) {
9614                 /* Crafty 9.29+ outputs this */
9615                 programStats.got_fail = 1;
9616                 return;
9617
9618             } else if (thinkOutput[0] != NULLCHAR &&
9619                        strncmp(message, "    ", 4) == 0) {
9620                 unsigned message_len;
9621
9622                 p = message;
9623                 while (*p && *p == ' ') p++;
9624
9625                 message_len = strlen( p );
9626
9627                 /* [AS] Avoid buffer overflow */
9628                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9629                     strcat(thinkOutput, " ");
9630                     strcat(thinkOutput, p);
9631                 }
9632
9633                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9634                     strcat(programStats.movelist, " ");
9635                     strcat(programStats.movelist, p);
9636                 }
9637
9638                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9639                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9640                     DisplayMove(currentMove - 1);
9641                 }
9642                 return;
9643             }
9644         }
9645         else {
9646             buf1[0] = NULLCHAR;
9647
9648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9650             {
9651                 ChessProgramStats cpstats;
9652
9653                 if (plyext != ' ' && plyext != '\t') {
9654                     time *= 100;
9655                 }
9656
9657                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9658                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9659                     curscore = -curscore;
9660                 }
9661
9662                 cpstats.depth = plylev;
9663                 cpstats.nodes = nodes;
9664                 cpstats.time = time;
9665                 cpstats.score = curscore;
9666                 cpstats.got_only_move = 0;
9667                 cpstats.movelist[0] = '\0';
9668
9669                 if (buf1[0] != NULLCHAR) {
9670                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9671                 }
9672
9673                 cpstats.ok_to_send = 0;
9674                 cpstats.line_is_book = 0;
9675                 cpstats.nr_moves = 0;
9676                 cpstats.moves_left = 0;
9677
9678                 SendProgramStatsToFrontend( cps, &cpstats );
9679             }
9680         }
9681     }
9682 }
9683
9684
9685 /* Parse a game score from the character string "game", and
9686    record it as the history of the current game.  The game
9687    score is NOT assumed to start from the standard position.
9688    The display is not updated in any way.
9689    */
9690 void
9691 ParseGameHistory (char *game)
9692 {
9693     ChessMove moveType;
9694     int fromX, fromY, toX, toY, boardIndex;
9695     char promoChar;
9696     char *p, *q;
9697     char buf[MSG_SIZ];
9698
9699     if (appData.debugMode)
9700       fprintf(debugFP, "Parsing game history: %s\n", game);
9701
9702     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9703     gameInfo.site = StrSave(appData.icsHost);
9704     gameInfo.date = PGNDate();
9705     gameInfo.round = StrSave("-");
9706
9707     /* Parse out names of players */
9708     while (*game == ' ') game++;
9709     p = buf;
9710     while (*game != ' ') *p++ = *game++;
9711     *p = NULLCHAR;
9712     gameInfo.white = StrSave(buf);
9713     while (*game == ' ') game++;
9714     p = buf;
9715     while (*game != ' ' && *game != '\n') *p++ = *game++;
9716     *p = NULLCHAR;
9717     gameInfo.black = StrSave(buf);
9718
9719     /* Parse moves */
9720     boardIndex = blackPlaysFirst ? 1 : 0;
9721     yynewstr(game);
9722     for (;;) {
9723         yyboardindex = boardIndex;
9724         moveType = (ChessMove) Myylex();
9725         switch (moveType) {
9726           case IllegalMove:             /* maybe suicide chess, etc. */
9727   if (appData.debugMode) {
9728     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9729     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9730     setbuf(debugFP, NULL);
9731   }
9732           case WhitePromotion:
9733           case BlackPromotion:
9734           case WhiteNonPromotion:
9735           case BlackNonPromotion:
9736           case NormalMove:
9737           case FirstLeg:
9738           case WhiteCapturesEnPassant:
9739           case BlackCapturesEnPassant:
9740           case WhiteKingSideCastle:
9741           case WhiteQueenSideCastle:
9742           case BlackKingSideCastle:
9743           case BlackQueenSideCastle:
9744           case WhiteKingSideCastleWild:
9745           case WhiteQueenSideCastleWild:
9746           case BlackKingSideCastleWild:
9747           case BlackQueenSideCastleWild:
9748           /* PUSH Fabien */
9749           case WhiteHSideCastleFR:
9750           case WhiteASideCastleFR:
9751           case BlackHSideCastleFR:
9752           case BlackASideCastleFR:
9753           /* POP Fabien */
9754             fromX = currentMoveString[0] - AAA;
9755             fromY = currentMoveString[1] - ONE;
9756             toX = currentMoveString[2] - AAA;
9757             toY = currentMoveString[3] - ONE;
9758             promoChar = currentMoveString[4];
9759             break;
9760           case WhiteDrop:
9761           case BlackDrop:
9762             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9763             fromX = moveType == WhiteDrop ?
9764               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9765             (int) CharToPiece(ToLower(currentMoveString[0]));
9766             fromY = DROP_RANK;
9767             toX = currentMoveString[2] - AAA;
9768             toY = currentMoveString[3] - ONE;
9769             promoChar = NULLCHAR;
9770             break;
9771           case AmbiguousMove:
9772             /* bug? */
9773             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9774   if (appData.debugMode) {
9775     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9776     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9777     setbuf(debugFP, NULL);
9778   }
9779             DisplayError(buf, 0);
9780             return;
9781           case ImpossibleMove:
9782             /* bug? */
9783             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9784   if (appData.debugMode) {
9785     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9787     setbuf(debugFP, NULL);
9788   }
9789             DisplayError(buf, 0);
9790             return;
9791           case EndOfFile:
9792             if (boardIndex < backwardMostMove) {
9793                 /* Oops, gap.  How did that happen? */
9794                 DisplayError(_("Gap in move list"), 0);
9795                 return;
9796             }
9797             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9798             if (boardIndex > forwardMostMove) {
9799                 forwardMostMove = boardIndex;
9800             }
9801             return;
9802           case ElapsedTime:
9803             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9804                 strcat(parseList[boardIndex-1], " ");
9805                 strcat(parseList[boardIndex-1], yy_text);
9806             }
9807             continue;
9808           case Comment:
9809           case PGNTag:
9810           case NAG:
9811           default:
9812             /* ignore */
9813             continue;
9814           case WhiteWins:
9815           case BlackWins:
9816           case GameIsDrawn:
9817           case GameUnfinished:
9818             if (gameMode == IcsExamining) {
9819                 if (boardIndex < backwardMostMove) {
9820                     /* Oops, gap.  How did that happen? */
9821                     return;
9822                 }
9823                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9824                 return;
9825             }
9826             gameInfo.result = moveType;
9827             p = strchr(yy_text, '{');
9828             if (p == NULL) p = strchr(yy_text, '(');
9829             if (p == NULL) {
9830                 p = yy_text;
9831                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9832             } else {
9833                 q = strchr(p, *p == '{' ? '}' : ')');
9834                 if (q != NULL) *q = NULLCHAR;
9835                 p++;
9836             }
9837             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9838             gameInfo.resultDetails = StrSave(p);
9839             continue;
9840         }
9841         if (boardIndex >= forwardMostMove &&
9842             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9843             backwardMostMove = blackPlaysFirst ? 1 : 0;
9844             return;
9845         }
9846         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9847                                  fromY, fromX, toY, toX, promoChar,
9848                                  parseList[boardIndex]);
9849         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9850         /* currentMoveString is set as a side-effect of yylex */
9851         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9852         strcat(moveList[boardIndex], "\n");
9853         boardIndex++;
9854         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9855         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9856           case MT_NONE:
9857           case MT_STALEMATE:
9858           default:
9859             break;
9860           case MT_CHECK:
9861             if(!IS_SHOGI(gameInfo.variant))
9862                 strcat(parseList[boardIndex - 1], "+");
9863             break;
9864           case MT_CHECKMATE:
9865           case MT_STAINMATE:
9866             strcat(parseList[boardIndex - 1], "#");
9867             break;
9868         }
9869     }
9870 }
9871
9872
9873 /* Apply a move to the given board  */
9874 void
9875 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9876 {
9877   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9878   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9879
9880     /* [HGM] compute & store e.p. status and castling rights for new position */
9881     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9882
9883       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9884       oldEP = (signed char)board[EP_STATUS];
9885       board[EP_STATUS] = EP_NONE;
9886       board[EP_FILE] = board[EP_RANK] = 100;
9887
9888   if (fromY == DROP_RANK) {
9889         /* must be first */
9890         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9891             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9892             return;
9893         }
9894         piece = board[toY][toX] = (ChessSquare) fromX;
9895   } else {
9896 //      ChessSquare victim;
9897       int i;
9898
9899       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9900 //           victim = board[killY][killX],
9901            board[killY][killX] = EmptySquare,
9902            board[EP_STATUS] = EP_CAPTURE;
9903
9904       if( board[toY][toX] != EmptySquare ) {
9905            board[EP_STATUS] = EP_CAPTURE;
9906            if( (fromX != toX || fromY != toY) && // not igui!
9907                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9908                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9909                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9910            }
9911       }
9912
9913       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9914            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9915                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9916       } else
9917       if( board[fromY][fromX] == WhitePawn ) {
9918            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9919                board[EP_STATUS] = EP_PAWN_MOVE;
9920            if( toY-fromY==2) {
9921                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9922                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9923                         gameInfo.variant != VariantBerolina || toX < fromX)
9924                       board[EP_STATUS] = toX | berolina;
9925                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9926                         gameInfo.variant != VariantBerolina || toX > fromX)
9927                       board[EP_STATUS] = toX;
9928            }
9929       } else
9930       if( board[fromY][fromX] == BlackPawn ) {
9931            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9932                board[EP_STATUS] = EP_PAWN_MOVE;
9933            if( toY-fromY== -2) {
9934                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9935                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9936                         gameInfo.variant != VariantBerolina || toX < fromX)
9937                       board[EP_STATUS] = toX | berolina;
9938                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9939                         gameInfo.variant != VariantBerolina || toX > fromX)
9940                       board[EP_STATUS] = toX;
9941            }
9942        }
9943
9944        for(i=0; i<nrCastlingRights; i++) {
9945            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9946               board[CASTLING][i] == toX   && castlingRank[i] == toY
9947              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9948        }
9949
9950        if(gameInfo.variant == VariantSChess) { // update virginity
9951            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9952            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9953            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9954            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9955        }
9956
9957      if (fromX == toX && fromY == toY) return;
9958
9959      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9960      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9961      if(gameInfo.variant == VariantKnightmate)
9962          king += (int) WhiteUnicorn - (int) WhiteKing;
9963
9964     /* Code added by Tord: */
9965     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9966     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9967         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9968       board[fromY][fromX] = EmptySquare;
9969       board[toY][toX] = EmptySquare;
9970       if((toX > fromX) != (piece == WhiteRook)) {
9971         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9972       } else {
9973         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9974       }
9975     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9976                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9977       board[fromY][fromX] = EmptySquare;
9978       board[toY][toX] = EmptySquare;
9979       if((toX > fromX) != (piece == BlackRook)) {
9980         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9981       } else {
9982         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9983       }
9984     /* End of code added by Tord */
9985
9986     } else if (board[fromY][fromX] == king
9987         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9988         && toY == fromY && toX > fromX+1) {
9989         board[fromY][fromX] = EmptySquare;
9990         board[toY][toX] = king;
9991         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9992         board[fromY][BOARD_RGHT-1] = EmptySquare;
9993     } else if (board[fromY][fromX] == king
9994         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9995                && toY == fromY && toX < fromX-1) {
9996         board[fromY][fromX] = EmptySquare;
9997         board[toY][toX] = king;
9998         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9999         board[fromY][BOARD_LEFT] = EmptySquare;
10000     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10001                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10002                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10003                ) {
10004         /* white pawn promotion */
10005         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10006         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10007             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10008         board[fromY][fromX] = EmptySquare;
10009     } else if ((fromY >= BOARD_HEIGHT>>1)
10010                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10011                && (toX != fromX)
10012                && gameInfo.variant != VariantXiangqi
10013                && gameInfo.variant != VariantBerolina
10014                && (board[fromY][fromX] == WhitePawn)
10015                && (board[toY][toX] == EmptySquare)) {
10016         board[fromY][fromX] = EmptySquare;
10017         board[toY][toX] = WhitePawn;
10018         captured = board[toY - 1][toX];
10019         board[toY - 1][toX] = EmptySquare;
10020     } else if ((fromY == BOARD_HEIGHT-4)
10021                && (toX == fromX)
10022                && gameInfo.variant == VariantBerolina
10023                && (board[fromY][fromX] == WhitePawn)
10024                && (board[toY][toX] == EmptySquare)) {
10025         board[fromY][fromX] = EmptySquare;
10026         board[toY][toX] = WhitePawn;
10027         if(oldEP & EP_BEROLIN_A) {
10028                 captured = board[fromY][fromX-1];
10029                 board[fromY][fromX-1] = EmptySquare;
10030         }else{  captured = board[fromY][fromX+1];
10031                 board[fromY][fromX+1] = EmptySquare;
10032         }
10033     } else if (board[fromY][fromX] == king
10034         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10035                && toY == fromY && toX > fromX+1) {
10036         board[fromY][fromX] = EmptySquare;
10037         board[toY][toX] = king;
10038         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10039         board[fromY][BOARD_RGHT-1] = EmptySquare;
10040     } else if (board[fromY][fromX] == king
10041         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10042                && toY == fromY && toX < fromX-1) {
10043         board[fromY][fromX] = EmptySquare;
10044         board[toY][toX] = king;
10045         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10046         board[fromY][BOARD_LEFT] = EmptySquare;
10047     } else if (fromY == 7 && fromX == 3
10048                && board[fromY][fromX] == BlackKing
10049                && toY == 7 && toX == 5) {
10050         board[fromY][fromX] = EmptySquare;
10051         board[toY][toX] = BlackKing;
10052         board[fromY][7] = EmptySquare;
10053         board[toY][4] = BlackRook;
10054     } else if (fromY == 7 && fromX == 3
10055                && board[fromY][fromX] == BlackKing
10056                && toY == 7 && toX == 1) {
10057         board[fromY][fromX] = EmptySquare;
10058         board[toY][toX] = BlackKing;
10059         board[fromY][0] = EmptySquare;
10060         board[toY][2] = BlackRook;
10061     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10062                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10063                && toY < promoRank && promoChar
10064                ) {
10065         /* black pawn promotion */
10066         board[toY][toX] = CharToPiece(ToLower(promoChar));
10067         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10068             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10069         board[fromY][fromX] = EmptySquare;
10070     } else if ((fromY < BOARD_HEIGHT>>1)
10071                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10072                && (toX != fromX)
10073                && gameInfo.variant != VariantXiangqi
10074                && gameInfo.variant != VariantBerolina
10075                && (board[fromY][fromX] == BlackPawn)
10076                && (board[toY][toX] == EmptySquare)) {
10077         board[fromY][fromX] = EmptySquare;
10078         board[toY][toX] = BlackPawn;
10079         captured = board[toY + 1][toX];
10080         board[toY + 1][toX] = EmptySquare;
10081     } else if ((fromY == 3)
10082                && (toX == fromX)
10083                && gameInfo.variant == VariantBerolina
10084                && (board[fromY][fromX] == BlackPawn)
10085                && (board[toY][toX] == EmptySquare)) {
10086         board[fromY][fromX] = EmptySquare;
10087         board[toY][toX] = BlackPawn;
10088         if(oldEP & EP_BEROLIN_A) {
10089                 captured = board[fromY][fromX-1];
10090                 board[fromY][fromX-1] = EmptySquare;
10091         }else{  captured = board[fromY][fromX+1];
10092                 board[fromY][fromX+1] = EmptySquare;
10093         }
10094     } else {
10095         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10096         board[fromY][fromX] = EmptySquare;
10097         board[toY][toX] = piece;
10098     }
10099   }
10100
10101     if (gameInfo.holdingsWidth != 0) {
10102
10103       /* !!A lot more code needs to be written to support holdings  */
10104       /* [HGM] OK, so I have written it. Holdings are stored in the */
10105       /* penultimate board files, so they are automaticlly stored   */
10106       /* in the game history.                                       */
10107       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10108                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10109         /* Delete from holdings, by decreasing count */
10110         /* and erasing image if necessary            */
10111         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10112         if(p < (int) BlackPawn) { /* white drop */
10113              p -= (int)WhitePawn;
10114                  p = PieceToNumber((ChessSquare)p);
10115              if(p >= gameInfo.holdingsSize) p = 0;
10116              if(--board[p][BOARD_WIDTH-2] <= 0)
10117                   board[p][BOARD_WIDTH-1] = EmptySquare;
10118              if((int)board[p][BOARD_WIDTH-2] < 0)
10119                         board[p][BOARD_WIDTH-2] = 0;
10120         } else {                  /* black drop */
10121              p -= (int)BlackPawn;
10122                  p = PieceToNumber((ChessSquare)p);
10123              if(p >= gameInfo.holdingsSize) p = 0;
10124              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10125                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10126              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10127                         board[BOARD_HEIGHT-1-p][1] = 0;
10128         }
10129       }
10130       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10131           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10132         /* [HGM] holdings: Add to holdings, if holdings exist */
10133         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10134                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10135                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10136         }
10137         p = (int) captured;
10138         if (p >= (int) BlackPawn) {
10139           p -= (int)BlackPawn;
10140           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10141                   /* in Shogi restore piece to its original  first */
10142                   captured = (ChessSquare) (DEMOTED captured);
10143                   p = DEMOTED p;
10144           }
10145           p = PieceToNumber((ChessSquare)p);
10146           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10147           board[p][BOARD_WIDTH-2]++;
10148           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10149         } else {
10150           p -= (int)WhitePawn;
10151           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10152                   captured = (ChessSquare) (DEMOTED captured);
10153                   p = DEMOTED p;
10154           }
10155           p = PieceToNumber((ChessSquare)p);
10156           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10157           board[BOARD_HEIGHT-1-p][1]++;
10158           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10159         }
10160       }
10161     } else if (gameInfo.variant == VariantAtomic) {
10162       if (captured != EmptySquare) {
10163         int y, x;
10164         for (y = toY-1; y <= toY+1; y++) {
10165           for (x = toX-1; x <= toX+1; x++) {
10166             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10167                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10168               board[y][x] = EmptySquare;
10169             }
10170           }
10171         }
10172         board[toY][toX] = EmptySquare;
10173       }
10174     }
10175
10176     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10177         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10178     } else
10179     if(promoChar == '+') {
10180         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10181         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10182         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10183           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10184     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10185         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10186         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10187            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10188         board[toY][toX] = newPiece;
10189     }
10190     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10191                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10192         // [HGM] superchess: take promotion piece out of holdings
10193         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10194         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10195             if(!--board[k][BOARD_WIDTH-2])
10196                 board[k][BOARD_WIDTH-1] = EmptySquare;
10197         } else {
10198             if(!--board[BOARD_HEIGHT-1-k][1])
10199                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10200         }
10201     }
10202 }
10203
10204 /* Updates forwardMostMove */
10205 void
10206 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10207 {
10208     int x = toX, y = toY;
10209     char *s = parseList[forwardMostMove];
10210     ChessSquare p = boards[forwardMostMove][toY][toX];
10211 //    forwardMostMove++; // [HGM] bare: moved downstream
10212
10213     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10214     (void) CoordsToAlgebraic(boards[forwardMostMove],
10215                              PosFlags(forwardMostMove),
10216                              fromY, fromX, y, x, promoChar,
10217                              s);
10218     if(killX >= 0 && killY >= 0)
10219         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10220
10221     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10222         int timeLeft; static int lastLoadFlag=0; int king, piece;
10223         piece = boards[forwardMostMove][fromY][fromX];
10224         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10225         if(gameInfo.variant == VariantKnightmate)
10226             king += (int) WhiteUnicorn - (int) WhiteKing;
10227         if(forwardMostMove == 0) {
10228             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10229                 fprintf(serverMoves, "%s;", UserName());
10230             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10231                 fprintf(serverMoves, "%s;", second.tidy);
10232             fprintf(serverMoves, "%s;", first.tidy);
10233             if(gameMode == MachinePlaysWhite)
10234                 fprintf(serverMoves, "%s;", UserName());
10235             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10236                 fprintf(serverMoves, "%s;", second.tidy);
10237         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10238         lastLoadFlag = loadFlag;
10239         // print base move
10240         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10241         // print castling suffix
10242         if( toY == fromY && piece == king ) {
10243             if(toX-fromX > 1)
10244                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10245             if(fromX-toX >1)
10246                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10247         }
10248         // e.p. suffix
10249         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10250              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10251              boards[forwardMostMove][toY][toX] == EmptySquare
10252              && fromX != toX && fromY != toY)
10253                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10254         // promotion suffix
10255         if(promoChar != NULLCHAR) {
10256             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10257                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10258                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10259             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10260         }
10261         if(!loadFlag) {
10262                 char buf[MOVE_LEN*2], *p; int len;
10263             fprintf(serverMoves, "/%d/%d",
10264                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10265             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10266             else                      timeLeft = blackTimeRemaining/1000;
10267             fprintf(serverMoves, "/%d", timeLeft);
10268                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10269                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10270                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10271                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10272             fprintf(serverMoves, "/%s", buf);
10273         }
10274         fflush(serverMoves);
10275     }
10276
10277     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10278         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10279       return;
10280     }
10281     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10282     if (commentList[forwardMostMove+1] != NULL) {
10283         free(commentList[forwardMostMove+1]);
10284         commentList[forwardMostMove+1] = NULL;
10285     }
10286     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10287     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10288     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10289     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10290     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10291     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10292     adjustedClock = FALSE;
10293     gameInfo.result = GameUnfinished;
10294     if (gameInfo.resultDetails != NULL) {
10295         free(gameInfo.resultDetails);
10296         gameInfo.resultDetails = NULL;
10297     }
10298     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10299                               moveList[forwardMostMove - 1]);
10300     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10301       case MT_NONE:
10302       case MT_STALEMATE:
10303       default:
10304         break;
10305       case MT_CHECK:
10306         if(!IS_SHOGI(gameInfo.variant))
10307             strcat(parseList[forwardMostMove - 1], "+");
10308         break;
10309       case MT_CHECKMATE:
10310       case MT_STAINMATE:
10311         strcat(parseList[forwardMostMove - 1], "#");
10312         break;
10313     }
10314 }
10315
10316 /* Updates currentMove if not pausing */
10317 void
10318 ShowMove (int fromX, int fromY, int toX, int toY)
10319 {
10320     int instant = (gameMode == PlayFromGameFile) ?
10321         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10322     if(appData.noGUI) return;
10323     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10324         if (!instant) {
10325             if (forwardMostMove == currentMove + 1) {
10326                 AnimateMove(boards[forwardMostMove - 1],
10327                             fromX, fromY, toX, toY);
10328             }
10329         }
10330         currentMove = forwardMostMove;
10331     }
10332
10333     killX = killY = -1; // [HGM] lion: used up
10334
10335     if (instant) return;
10336
10337     DisplayMove(currentMove - 1);
10338     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10339             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10340                 SetHighlights(fromX, fromY, toX, toY);
10341             }
10342     }
10343     DrawPosition(FALSE, boards[currentMove]);
10344     DisplayBothClocks();
10345     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10346 }
10347
10348 void
10349 SendEgtPath (ChessProgramState *cps)
10350 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10351         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10352
10353         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10354
10355         while(*p) {
10356             char c, *q = name+1, *r, *s;
10357
10358             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10359             while(*p && *p != ',') *q++ = *p++;
10360             *q++ = ':'; *q = 0;
10361             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10362                 strcmp(name, ",nalimov:") == 0 ) {
10363                 // take nalimov path from the menu-changeable option first, if it is defined
10364               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10365                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10366             } else
10367             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10368                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10369                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10370                 s = r = StrStr(s, ":") + 1; // beginning of path info
10371                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10372                 c = *r; *r = 0;             // temporarily null-terminate path info
10373                     *--q = 0;               // strip of trailig ':' from name
10374                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10375                 *r = c;
10376                 SendToProgram(buf,cps);     // send egtbpath command for this format
10377             }
10378             if(*p == ',') p++; // read away comma to position for next format name
10379         }
10380 }
10381
10382 static int
10383 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10384 {
10385       int width = 8, height = 8, holdings = 0;             // most common sizes
10386       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10387       // correct the deviations default for each variant
10388       if( v == VariantXiangqi ) width = 9,  height = 10;
10389       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10390       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10391       if( v == VariantCapablanca || v == VariantCapaRandom ||
10392           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10393                                 width = 10;
10394       if( v == VariantCourier ) width = 12;
10395       if( v == VariantSuper )                            holdings = 8;
10396       if( v == VariantGreat )   width = 10,              holdings = 8;
10397       if( v == VariantSChess )                           holdings = 7;
10398       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10399       if( v == VariantChuChess) width = 10, height = 10;
10400       if( v == VariantChu )     width = 12, height = 12;
10401       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10402              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10403              holdingsSize >= 0 && holdingsSize != holdings;
10404 }
10405
10406 char variantError[MSG_SIZ];
10407
10408 char *
10409 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10410 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10411       char *p, *variant = VariantName(v);
10412       static char b[MSG_SIZ];
10413       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10414            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10415                                                holdingsSize, variant); // cook up sized variant name
10416            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10417            if(StrStr(list, b) == NULL) {
10418                // specific sized variant not known, check if general sizing allowed
10419                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10420                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10421                             boardWidth, boardHeight, holdingsSize, engine);
10422                    return NULL;
10423                }
10424                /* [HGM] here we really should compare with the maximum supported board size */
10425            }
10426       } else snprintf(b, MSG_SIZ,"%s", variant);
10427       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10428       p = StrStr(list, b);
10429       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10430       if(p == NULL) {
10431           // occurs not at all in list, or only as sub-string
10432           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10433           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10434               int l = strlen(variantError);
10435               char *q;
10436               while(p != list && p[-1] != ',') p--;
10437               q = strchr(p, ',');
10438               if(q) *q = NULLCHAR;
10439               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10440               if(q) *q= ',';
10441           }
10442           return NULL;
10443       }
10444       return b;
10445 }
10446
10447 void
10448 InitChessProgram (ChessProgramState *cps, int setup)
10449 /* setup needed to setup FRC opening position */
10450 {
10451     char buf[MSG_SIZ], *b;
10452     if (appData.noChessProgram) return;
10453     hintRequested = FALSE;
10454     bookRequested = FALSE;
10455
10456     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10457     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10458     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10459     if(cps->memSize) { /* [HGM] memory */
10460       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10461         SendToProgram(buf, cps);
10462     }
10463     SendEgtPath(cps); /* [HGM] EGT */
10464     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10465       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10466         SendToProgram(buf, cps);
10467     }
10468
10469     setboardSpoiledMachineBlack = FALSE;
10470     SendToProgram(cps->initString, cps);
10471     if (gameInfo.variant != VariantNormal &&
10472         gameInfo.variant != VariantLoadable
10473         /* [HGM] also send variant if board size non-standard */
10474         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10475
10476       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10477                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10478       if (b == NULL) {
10479         DisplayFatalError(variantError, 0, 1);
10480         return;
10481       }
10482
10483       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10484       SendToProgram(buf, cps);
10485     }
10486     currentlyInitializedVariant = gameInfo.variant;
10487
10488     /* [HGM] send opening position in FRC to first engine */
10489     if(setup) {
10490           SendToProgram("force\n", cps);
10491           SendBoard(cps, 0);
10492           /* engine is now in force mode! Set flag to wake it up after first move. */
10493           setboardSpoiledMachineBlack = 1;
10494     }
10495
10496     if (cps->sendICS) {
10497       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10498       SendToProgram(buf, cps);
10499     }
10500     cps->maybeThinking = FALSE;
10501     cps->offeredDraw = 0;
10502     if (!appData.icsActive) {
10503         SendTimeControl(cps, movesPerSession, timeControl,
10504                         timeIncrement, appData.searchDepth,
10505                         searchTime);
10506     }
10507     if (appData.showThinking
10508         // [HGM] thinking: four options require thinking output to be sent
10509         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10510                                 ) {
10511         SendToProgram("post\n", cps);
10512     }
10513     SendToProgram("hard\n", cps);
10514     if (!appData.ponderNextMove) {
10515         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10516            it without being sure what state we are in first.  "hard"
10517            is not a toggle, so that one is OK.
10518          */
10519         SendToProgram("easy\n", cps);
10520     }
10521     if (cps->usePing) {
10522       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10523       SendToProgram(buf, cps);
10524     }
10525     cps->initDone = TRUE;
10526     ClearEngineOutputPane(cps == &second);
10527 }
10528
10529
10530 void
10531 ResendOptions (ChessProgramState *cps)
10532 { // send the stored value of the options
10533   int i;
10534   char buf[MSG_SIZ];
10535   Option *opt = cps->option;
10536   for(i=0; i<cps->nrOptions; i++, opt++) {
10537       switch(opt->type) {
10538         case Spin:
10539         case Slider:
10540         case CheckBox:
10541             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10542           break;
10543         case ComboBox:
10544           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10545           break;
10546         default:
10547             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10548           break;
10549         case Button:
10550         case SaveButton:
10551           continue;
10552       }
10553       SendToProgram(buf, cps);
10554   }
10555 }
10556
10557 void
10558 StartChessProgram (ChessProgramState *cps)
10559 {
10560     char buf[MSG_SIZ];
10561     int err;
10562
10563     if (appData.noChessProgram) return;
10564     cps->initDone = FALSE;
10565
10566     if (strcmp(cps->host, "localhost") == 0) {
10567         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10568     } else if (*appData.remoteShell == NULLCHAR) {
10569         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10570     } else {
10571         if (*appData.remoteUser == NULLCHAR) {
10572           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10573                     cps->program);
10574         } else {
10575           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10576                     cps->host, appData.remoteUser, cps->program);
10577         }
10578         err = StartChildProcess(buf, "", &cps->pr);
10579     }
10580
10581     if (err != 0) {
10582       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10583         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10584         if(cps != &first) return;
10585         appData.noChessProgram = TRUE;
10586         ThawUI();
10587         SetNCPMode();
10588 //      DisplayFatalError(buf, err, 1);
10589 //      cps->pr = NoProc;
10590 //      cps->isr = NULL;
10591         return;
10592     }
10593
10594     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10595     if (cps->protocolVersion > 1) {
10596       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10597       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10598         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10599         cps->comboCnt = 0;  //                and values of combo boxes
10600       }
10601       SendToProgram(buf, cps);
10602       if(cps->reload) ResendOptions(cps);
10603     } else {
10604       SendToProgram("xboard\n", cps);
10605     }
10606 }
10607
10608 void
10609 TwoMachinesEventIfReady P((void))
10610 {
10611   static int curMess = 0;
10612   if (first.lastPing != first.lastPong) {
10613     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10614     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10615     return;
10616   }
10617   if (second.lastPing != second.lastPong) {
10618     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10619     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10620     return;
10621   }
10622   DisplayMessage("", ""); curMess = 0;
10623   TwoMachinesEvent();
10624 }
10625
10626 char *
10627 MakeName (char *template)
10628 {
10629     time_t clock;
10630     struct tm *tm;
10631     static char buf[MSG_SIZ];
10632     char *p = buf;
10633     int i;
10634
10635     clock = time((time_t *)NULL);
10636     tm = localtime(&clock);
10637
10638     while(*p++ = *template++) if(p[-1] == '%') {
10639         switch(*template++) {
10640           case 0:   *p = 0; return buf;
10641           case 'Y': i = tm->tm_year+1900; break;
10642           case 'y': i = tm->tm_year-100; break;
10643           case 'M': i = tm->tm_mon+1; break;
10644           case 'd': i = tm->tm_mday; break;
10645           case 'h': i = tm->tm_hour; break;
10646           case 'm': i = tm->tm_min; break;
10647           case 's': i = tm->tm_sec; break;
10648           default:  i = 0;
10649         }
10650         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10651     }
10652     return buf;
10653 }
10654
10655 int
10656 CountPlayers (char *p)
10657 {
10658     int n = 0;
10659     while(p = strchr(p, '\n')) p++, n++; // count participants
10660     return n;
10661 }
10662
10663 FILE *
10664 WriteTourneyFile (char *results, FILE *f)
10665 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10666     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10667     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10668         // create a file with tournament description
10669         fprintf(f, "-participants {%s}\n", appData.participants);
10670         fprintf(f, "-seedBase %d\n", appData.seedBase);
10671         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10672         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10673         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10674         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10675         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10676         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10677         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10678         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10679         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10680         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10681         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10682         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10683         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10684         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10685         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10686         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10687         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10688         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10689         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10690         fprintf(f, "-smpCores %d\n", appData.smpCores);
10691         if(searchTime > 0)
10692                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10693         else {
10694                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10695                 fprintf(f, "-tc %s\n", appData.timeControl);
10696                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10697         }
10698         fprintf(f, "-results \"%s\"\n", results);
10699     }
10700     return f;
10701 }
10702
10703 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10704
10705 void
10706 Substitute (char *participants, int expunge)
10707 {
10708     int i, changed, changes=0, nPlayers=0;
10709     char *p, *q, *r, buf[MSG_SIZ];
10710     if(participants == NULL) return;
10711     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10712     r = p = participants; q = appData.participants;
10713     while(*p && *p == *q) {
10714         if(*p == '\n') r = p+1, nPlayers++;
10715         p++; q++;
10716     }
10717     if(*p) { // difference
10718         while(*p && *p++ != '\n');
10719         while(*q && *q++ != '\n');
10720       changed = nPlayers;
10721         changes = 1 + (strcmp(p, q) != 0);
10722     }
10723     if(changes == 1) { // a single engine mnemonic was changed
10724         q = r; while(*q) nPlayers += (*q++ == '\n');
10725         p = buf; while(*r && (*p = *r++) != '\n') p++;
10726         *p = NULLCHAR;
10727         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10728         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10729         if(mnemonic[i]) { // The substitute is valid
10730             FILE *f;
10731             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10732                 flock(fileno(f), LOCK_EX);
10733                 ParseArgsFromFile(f);
10734                 fseek(f, 0, SEEK_SET);
10735                 FREE(appData.participants); appData.participants = participants;
10736                 if(expunge) { // erase results of replaced engine
10737                     int len = strlen(appData.results), w, b, dummy;
10738                     for(i=0; i<len; i++) {
10739                         Pairing(i, nPlayers, &w, &b, &dummy);
10740                         if((w == changed || b == changed) && appData.results[i] == '*') {
10741                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10742                             fclose(f);
10743                             return;
10744                         }
10745                     }
10746                     for(i=0; i<len; i++) {
10747                         Pairing(i, nPlayers, &w, &b, &dummy);
10748                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10749                     }
10750                 }
10751                 WriteTourneyFile(appData.results, f);
10752                 fclose(f); // release lock
10753                 return;
10754             }
10755         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10756     }
10757     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10758     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10759     free(participants);
10760     return;
10761 }
10762
10763 int
10764 CheckPlayers (char *participants)
10765 {
10766         int i;
10767         char buf[MSG_SIZ], *p;
10768         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10769         while(p = strchr(participants, '\n')) {
10770             *p = NULLCHAR;
10771             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10772             if(!mnemonic[i]) {
10773                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10774                 *p = '\n';
10775                 DisplayError(buf, 0);
10776                 return 1;
10777             }
10778             *p = '\n';
10779             participants = p + 1;
10780         }
10781         return 0;
10782 }
10783
10784 int
10785 CreateTourney (char *name)
10786 {
10787         FILE *f;
10788         if(matchMode && strcmp(name, appData.tourneyFile)) {
10789              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10790         }
10791         if(name[0] == NULLCHAR) {
10792             if(appData.participants[0])
10793                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10794             return 0;
10795         }
10796         f = fopen(name, "r");
10797         if(f) { // file exists
10798             ASSIGN(appData.tourneyFile, name);
10799             ParseArgsFromFile(f); // parse it
10800         } else {
10801             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10802             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10803                 DisplayError(_("Not enough participants"), 0);
10804                 return 0;
10805             }
10806             if(CheckPlayers(appData.participants)) return 0;
10807             ASSIGN(appData.tourneyFile, name);
10808             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10809             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10810         }
10811         fclose(f);
10812         appData.noChessProgram = FALSE;
10813         appData.clockMode = TRUE;
10814         SetGNUMode();
10815         return 1;
10816 }
10817
10818 int
10819 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10820 {
10821     char buf[MSG_SIZ], *p, *q;
10822     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10823     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10824     skip = !all && group[0]; // if group requested, we start in skip mode
10825     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10826         p = names; q = buf; header = 0;
10827         while(*p && *p != '\n') *q++ = *p++;
10828         *q = 0;
10829         if(*p == '\n') p++;
10830         if(buf[0] == '#') {
10831             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10832             depth++; // we must be entering a new group
10833             if(all) continue; // suppress printing group headers when complete list requested
10834             header = 1;
10835             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10836         }
10837         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10838         if(engineList[i]) free(engineList[i]);
10839         engineList[i] = strdup(buf);
10840         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10841         if(engineMnemonic[i]) free(engineMnemonic[i]);
10842         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10843             strcat(buf, " (");
10844             sscanf(q + 8, "%s", buf + strlen(buf));
10845             strcat(buf, ")");
10846         }
10847         engineMnemonic[i] = strdup(buf);
10848         i++;
10849     }
10850     engineList[i] = engineMnemonic[i] = NULL;
10851     return i;
10852 }
10853
10854 // following implemented as macro to avoid type limitations
10855 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10856
10857 void
10858 SwapEngines (int n)
10859 {   // swap settings for first engine and other engine (so far only some selected options)
10860     int h;
10861     char *p;
10862     if(n == 0) return;
10863     SWAP(directory, p)
10864     SWAP(chessProgram, p)
10865     SWAP(isUCI, h)
10866     SWAP(hasOwnBookUCI, h)
10867     SWAP(protocolVersion, h)
10868     SWAP(reuse, h)
10869     SWAP(scoreIsAbsolute, h)
10870     SWAP(timeOdds, h)
10871     SWAP(logo, p)
10872     SWAP(pgnName, p)
10873     SWAP(pvSAN, h)
10874     SWAP(engOptions, p)
10875     SWAP(engInitString, p)
10876     SWAP(computerString, p)
10877     SWAP(features, p)
10878     SWAP(fenOverride, p)
10879     SWAP(NPS, h)
10880     SWAP(accumulateTC, h)
10881     SWAP(drawDepth, h)
10882     SWAP(host, p)
10883     SWAP(pseudo, h)
10884 }
10885
10886 int
10887 GetEngineLine (char *s, int n)
10888 {
10889     int i;
10890     char buf[MSG_SIZ];
10891     extern char *icsNames;
10892     if(!s || !*s) return 0;
10893     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10894     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10895     if(!mnemonic[i]) return 0;
10896     if(n == 11) return 1; // just testing if there was a match
10897     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10898     if(n == 1) SwapEngines(n);
10899     ParseArgsFromString(buf);
10900     if(n == 1) SwapEngines(n);
10901     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10902         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10903         ParseArgsFromString(buf);
10904     }
10905     return 1;
10906 }
10907
10908 int
10909 SetPlayer (int player, char *p)
10910 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10911     int i;
10912     char buf[MSG_SIZ], *engineName;
10913     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10914     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10915     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10916     if(mnemonic[i]) {
10917         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10918         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10919         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10920         ParseArgsFromString(buf);
10921     } else { // no engine with this nickname is installed!
10922         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10923         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10924         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10925         ModeHighlight();
10926         DisplayError(buf, 0);
10927         return 0;
10928     }
10929     free(engineName);
10930     return i;
10931 }
10932
10933 char *recentEngines;
10934
10935 void
10936 RecentEngineEvent (int nr)
10937 {
10938     int n;
10939 //    SwapEngines(1); // bump first to second
10940 //    ReplaceEngine(&second, 1); // and load it there
10941     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10942     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10943     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10944         ReplaceEngine(&first, 0);
10945         FloatToFront(&appData.recentEngineList, command[n]);
10946     }
10947 }
10948
10949 int
10950 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10951 {   // determine players from game number
10952     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10953
10954     if(appData.tourneyType == 0) {
10955         roundsPerCycle = (nPlayers - 1) | 1;
10956         pairingsPerRound = nPlayers / 2;
10957     } else if(appData.tourneyType > 0) {
10958         roundsPerCycle = nPlayers - appData.tourneyType;
10959         pairingsPerRound = appData.tourneyType;
10960     }
10961     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10962     gamesPerCycle = gamesPerRound * roundsPerCycle;
10963     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10964     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10965     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10966     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10967     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10968     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10969
10970     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10971     if(appData.roundSync) *syncInterval = gamesPerRound;
10972
10973     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10974
10975     if(appData.tourneyType == 0) {
10976         if(curPairing == (nPlayers-1)/2 ) {
10977             *whitePlayer = curRound;
10978             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10979         } else {
10980             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10981             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10982             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10983             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10984         }
10985     } else if(appData.tourneyType > 1) {
10986         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10987         *whitePlayer = curRound + appData.tourneyType;
10988     } else if(appData.tourneyType > 0) {
10989         *whitePlayer = curPairing;
10990         *blackPlayer = curRound + appData.tourneyType;
10991     }
10992
10993     // take care of white/black alternation per round.
10994     // For cycles and games this is already taken care of by default, derived from matchGame!
10995     return curRound & 1;
10996 }
10997
10998 int
10999 NextTourneyGame (int nr, int *swapColors)
11000 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11001     char *p, *q;
11002     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11003     FILE *tf;
11004     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11005     tf = fopen(appData.tourneyFile, "r");
11006     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11007     ParseArgsFromFile(tf); fclose(tf);
11008     InitTimeControls(); // TC might be altered from tourney file
11009
11010     nPlayers = CountPlayers(appData.participants); // count participants
11011     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11012     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11013
11014     if(syncInterval) {
11015         p = q = appData.results;
11016         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11017         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11018             DisplayMessage(_("Waiting for other game(s)"),"");
11019             waitingForGame = TRUE;
11020             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11021             return 0;
11022         }
11023         waitingForGame = FALSE;
11024     }
11025
11026     if(appData.tourneyType < 0) {
11027         if(nr>=0 && !pairingReceived) {
11028             char buf[1<<16];
11029             if(pairing.pr == NoProc) {
11030                 if(!appData.pairingEngine[0]) {
11031                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11032                     return 0;
11033                 }
11034                 StartChessProgram(&pairing); // starts the pairing engine
11035             }
11036             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11037             SendToProgram(buf, &pairing);
11038             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11039             SendToProgram(buf, &pairing);
11040             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11041         }
11042         pairingReceived = 0;                              // ... so we continue here
11043         *swapColors = 0;
11044         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11045         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11046         matchGame = 1; roundNr = nr / syncInterval + 1;
11047     }
11048
11049     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11050
11051     // redefine engines, engine dir, etc.
11052     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11053     if(first.pr == NoProc) {
11054       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11055       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11056     }
11057     if(second.pr == NoProc) {
11058       SwapEngines(1);
11059       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11060       SwapEngines(1);         // and make that valid for second engine by swapping
11061       InitEngine(&second, 1);
11062     }
11063     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11064     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11065     return OK;
11066 }
11067
11068 void
11069 NextMatchGame ()
11070 {   // performs game initialization that does not invoke engines, and then tries to start the game
11071     int res, firstWhite, swapColors = 0;
11072     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11073     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
11074         char buf[MSG_SIZ];
11075         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11076         if(strcmp(buf, currentDebugFile)) { // name has changed
11077             FILE *f = fopen(buf, "w");
11078             if(f) { // if opening the new file failed, just keep using the old one
11079                 ASSIGN(currentDebugFile, buf);
11080                 fclose(debugFP);
11081                 debugFP = f;
11082             }
11083             if(appData.serverFileName) {
11084                 if(serverFP) fclose(serverFP);
11085                 serverFP = fopen(appData.serverFileName, "w");
11086                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11087                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11088             }
11089         }
11090     }
11091     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11092     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11093     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11094     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11095     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11096     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11097     Reset(FALSE, first.pr != NoProc);
11098     res = LoadGameOrPosition(matchGame); // setup game
11099     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11100     if(!res) return; // abort when bad game/pos file
11101     TwoMachinesEvent();
11102 }
11103
11104 void
11105 UserAdjudicationEvent (int result)
11106 {
11107     ChessMove gameResult = GameIsDrawn;
11108
11109     if( result > 0 ) {
11110         gameResult = WhiteWins;
11111     }
11112     else if( result < 0 ) {
11113         gameResult = BlackWins;
11114     }
11115
11116     if( gameMode == TwoMachinesPlay ) {
11117         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11118     }
11119 }
11120
11121
11122 // [HGM] save: calculate checksum of game to make games easily identifiable
11123 int
11124 StringCheckSum (char *s)
11125 {
11126         int i = 0;
11127         if(s==NULL) return 0;
11128         while(*s) i = i*259 + *s++;
11129         return i;
11130 }
11131
11132 int
11133 GameCheckSum ()
11134 {
11135         int i, sum=0;
11136         for(i=backwardMostMove; i<forwardMostMove; i++) {
11137                 sum += pvInfoList[i].depth;
11138                 sum += StringCheckSum(parseList[i]);
11139                 sum += StringCheckSum(commentList[i]);
11140                 sum *= 261;
11141         }
11142         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11143         return sum + StringCheckSum(commentList[i]);
11144 } // end of save patch
11145
11146 void
11147 GameEnds (ChessMove result, char *resultDetails, int whosays)
11148 {
11149     GameMode nextGameMode;
11150     int isIcsGame;
11151     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11152
11153     if(endingGame) return; /* [HGM] crash: forbid recursion */
11154     endingGame = 1;
11155     if(twoBoards) { // [HGM] dual: switch back to one board
11156         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11157         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11158     }
11159     if (appData.debugMode) {
11160       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11161               result, resultDetails ? resultDetails : "(null)", whosays);
11162     }
11163
11164     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11165
11166     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11167
11168     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11169         /* If we are playing on ICS, the server decides when the
11170            game is over, but the engine can offer to draw, claim
11171            a draw, or resign.
11172          */
11173 #if ZIPPY
11174         if (appData.zippyPlay && first.initDone) {
11175             if (result == GameIsDrawn) {
11176                 /* In case draw still needs to be claimed */
11177                 SendToICS(ics_prefix);
11178                 SendToICS("draw\n");
11179             } else if (StrCaseStr(resultDetails, "resign")) {
11180                 SendToICS(ics_prefix);
11181                 SendToICS("resign\n");
11182             }
11183         }
11184 #endif
11185         endingGame = 0; /* [HGM] crash */
11186         return;
11187     }
11188
11189     /* If we're loading the game from a file, stop */
11190     if (whosays == GE_FILE) {
11191       (void) StopLoadGameTimer();
11192       gameFileFP = NULL;
11193     }
11194
11195     /* Cancel draw offers */
11196     first.offeredDraw = second.offeredDraw = 0;
11197
11198     /* If this is an ICS game, only ICS can really say it's done;
11199        if not, anyone can. */
11200     isIcsGame = (gameMode == IcsPlayingWhite ||
11201                  gameMode == IcsPlayingBlack ||
11202                  gameMode == IcsObserving    ||
11203                  gameMode == IcsExamining);
11204
11205     if (!isIcsGame || whosays == GE_ICS) {
11206         /* OK -- not an ICS game, or ICS said it was done */
11207         StopClocks();
11208         if (!isIcsGame && !appData.noChessProgram)
11209           SetUserThinkingEnables();
11210
11211         /* [HGM] if a machine claims the game end we verify this claim */
11212         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11213             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11214                 char claimer;
11215                 ChessMove trueResult = (ChessMove) -1;
11216
11217                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11218                                             first.twoMachinesColor[0] :
11219                                             second.twoMachinesColor[0] ;
11220
11221                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11222                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11223                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11224                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11225                 } else
11226                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11227                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11228                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11229                 } else
11230                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11231                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11232                 }
11233
11234                 // now verify win claims, but not in drop games, as we don't understand those yet
11235                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11236                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11237                     (result == WhiteWins && claimer == 'w' ||
11238                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11239                       if (appData.debugMode) {
11240                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11241                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11242                       }
11243                       if(result != trueResult) {
11244                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11245                               result = claimer == 'w' ? BlackWins : WhiteWins;
11246                               resultDetails = buf;
11247                       }
11248                 } else
11249                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11250                     && (forwardMostMove <= backwardMostMove ||
11251                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11252                         (claimer=='b')==(forwardMostMove&1))
11253                                                                                   ) {
11254                       /* [HGM] verify: draws that were not flagged are false claims */
11255                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11256                       result = claimer == 'w' ? BlackWins : WhiteWins;
11257                       resultDetails = buf;
11258                 }
11259                 /* (Claiming a loss is accepted no questions asked!) */
11260             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11261                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11262                 result = GameUnfinished;
11263                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11264             }
11265             /* [HGM] bare: don't allow bare King to win */
11266             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11267                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11268                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11269                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11270                && result != GameIsDrawn)
11271             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11272                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11273                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11274                         if(p >= 0 && p <= (int)WhiteKing) k++;
11275                 }
11276                 if (appData.debugMode) {
11277                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11278                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11279                 }
11280                 if(k <= 1) {
11281                         result = GameIsDrawn;
11282                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11283                         resultDetails = buf;
11284                 }
11285             }
11286         }
11287
11288
11289         if(serverMoves != NULL && !loadFlag) { char c = '=';
11290             if(result==WhiteWins) c = '+';
11291             if(result==BlackWins) c = '-';
11292             if(resultDetails != NULL)
11293                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11294         }
11295         if (resultDetails != NULL) {
11296             gameInfo.result = result;
11297             gameInfo.resultDetails = StrSave(resultDetails);
11298
11299             /* display last move only if game was not loaded from file */
11300             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11301                 DisplayMove(currentMove - 1);
11302
11303             if (forwardMostMove != 0) {
11304                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11305                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11306                                                                 ) {
11307                     if (*appData.saveGameFile != NULLCHAR) {
11308                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11309                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11310                         else
11311                         SaveGameToFile(appData.saveGameFile, TRUE);
11312                     } else if (appData.autoSaveGames) {
11313                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11314                     }
11315                     if (*appData.savePositionFile != NULLCHAR) {
11316                         SavePositionToFile(appData.savePositionFile);
11317                     }
11318                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11319                 }
11320             }
11321
11322             /* Tell program how game ended in case it is learning */
11323             /* [HGM] Moved this to after saving the PGN, just in case */
11324             /* engine died and we got here through time loss. In that */
11325             /* case we will get a fatal error writing the pipe, which */
11326             /* would otherwise lose us the PGN.                       */
11327             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11328             /* output during GameEnds should never be fatal anymore   */
11329             if (gameMode == MachinePlaysWhite ||
11330                 gameMode == MachinePlaysBlack ||
11331                 gameMode == TwoMachinesPlay ||
11332                 gameMode == IcsPlayingWhite ||
11333                 gameMode == IcsPlayingBlack ||
11334                 gameMode == BeginningOfGame) {
11335                 char buf[MSG_SIZ];
11336                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11337                         resultDetails);
11338                 if (first.pr != NoProc) {
11339                     SendToProgram(buf, &first);
11340                 }
11341                 if (second.pr != NoProc &&
11342                     gameMode == TwoMachinesPlay) {
11343                     SendToProgram(buf, &second);
11344                 }
11345             }
11346         }
11347
11348         if (appData.icsActive) {
11349             if (appData.quietPlay &&
11350                 (gameMode == IcsPlayingWhite ||
11351                  gameMode == IcsPlayingBlack)) {
11352                 SendToICS(ics_prefix);
11353                 SendToICS("set shout 1\n");
11354             }
11355             nextGameMode = IcsIdle;
11356             ics_user_moved = FALSE;
11357             /* clean up premove.  It's ugly when the game has ended and the
11358              * premove highlights are still on the board.
11359              */
11360             if (gotPremove) {
11361               gotPremove = FALSE;
11362               ClearPremoveHighlights();
11363               DrawPosition(FALSE, boards[currentMove]);
11364             }
11365             if (whosays == GE_ICS) {
11366                 switch (result) {
11367                 case WhiteWins:
11368                     if (gameMode == IcsPlayingWhite)
11369                         PlayIcsWinSound();
11370                     else if(gameMode == IcsPlayingBlack)
11371                         PlayIcsLossSound();
11372                     break;
11373                 case BlackWins:
11374                     if (gameMode == IcsPlayingBlack)
11375                         PlayIcsWinSound();
11376                     else if(gameMode == IcsPlayingWhite)
11377                         PlayIcsLossSound();
11378                     break;
11379                 case GameIsDrawn:
11380                     PlayIcsDrawSound();
11381                     break;
11382                 default:
11383                     PlayIcsUnfinishedSound();
11384                 }
11385             }
11386             if(appData.quitNext) { ExitEvent(0); return; }
11387         } else if (gameMode == EditGame ||
11388                    gameMode == PlayFromGameFile ||
11389                    gameMode == AnalyzeMode ||
11390                    gameMode == AnalyzeFile) {
11391             nextGameMode = gameMode;
11392         } else {
11393             nextGameMode = EndOfGame;
11394         }
11395         pausing = FALSE;
11396         ModeHighlight();
11397     } else {
11398         nextGameMode = gameMode;
11399     }
11400
11401     if (appData.noChessProgram) {
11402         gameMode = nextGameMode;
11403         ModeHighlight();
11404         endingGame = 0; /* [HGM] crash */
11405         return;
11406     }
11407
11408     if (first.reuse) {
11409         /* Put first chess program into idle state */
11410         if (first.pr != NoProc &&
11411             (gameMode == MachinePlaysWhite ||
11412              gameMode == MachinePlaysBlack ||
11413              gameMode == TwoMachinesPlay ||
11414              gameMode == IcsPlayingWhite ||
11415              gameMode == IcsPlayingBlack ||
11416              gameMode == BeginningOfGame)) {
11417             SendToProgram("force\n", &first);
11418             if (first.usePing) {
11419               char buf[MSG_SIZ];
11420               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11421               SendToProgram(buf, &first);
11422             }
11423         }
11424     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11425         /* Kill off first chess program */
11426         if (first.isr != NULL)
11427           RemoveInputSource(first.isr);
11428         first.isr = NULL;
11429
11430         if (first.pr != NoProc) {
11431             ExitAnalyzeMode();
11432             DoSleep( appData.delayBeforeQuit );
11433             SendToProgram("quit\n", &first);
11434             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11435             first.reload = TRUE;
11436         }
11437         first.pr = NoProc;
11438     }
11439     if (second.reuse) {
11440         /* Put second chess program into idle state */
11441         if (second.pr != NoProc &&
11442             gameMode == TwoMachinesPlay) {
11443             SendToProgram("force\n", &second);
11444             if (second.usePing) {
11445               char buf[MSG_SIZ];
11446               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11447               SendToProgram(buf, &second);
11448             }
11449         }
11450     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11451         /* Kill off second chess program */
11452         if (second.isr != NULL)
11453           RemoveInputSource(second.isr);
11454         second.isr = NULL;
11455
11456         if (second.pr != NoProc) {
11457             DoSleep( appData.delayBeforeQuit );
11458             SendToProgram("quit\n", &second);
11459             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11460             second.reload = TRUE;
11461         }
11462         second.pr = NoProc;
11463     }
11464
11465     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11466         char resChar = '=';
11467         switch (result) {
11468         case WhiteWins:
11469           resChar = '+';
11470           if (first.twoMachinesColor[0] == 'w') {
11471             first.matchWins++;
11472           } else {
11473             second.matchWins++;
11474           }
11475           break;
11476         case BlackWins:
11477           resChar = '-';
11478           if (first.twoMachinesColor[0] == 'b') {
11479             first.matchWins++;
11480           } else {
11481             second.matchWins++;
11482           }
11483           break;
11484         case GameUnfinished:
11485           resChar = ' ';
11486         default:
11487           break;
11488         }
11489
11490         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11491         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11492             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11493             ReserveGame(nextGame, resChar); // sets nextGame
11494             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11495             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11496         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11497
11498         if (nextGame <= appData.matchGames && !abortMatch) {
11499             gameMode = nextGameMode;
11500             matchGame = nextGame; // this will be overruled in tourney mode!
11501             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11502             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11503             endingGame = 0; /* [HGM] crash */
11504             return;
11505         } else {
11506             gameMode = nextGameMode;
11507             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11508                      first.tidy, second.tidy,
11509                      first.matchWins, second.matchWins,
11510                      appData.matchGames - (first.matchWins + second.matchWins));
11511             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11512             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11513             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11514             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11515                 first.twoMachinesColor = "black\n";
11516                 second.twoMachinesColor = "white\n";
11517             } else {
11518                 first.twoMachinesColor = "white\n";
11519                 second.twoMachinesColor = "black\n";
11520             }
11521         }
11522     }
11523     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11524         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11525       ExitAnalyzeMode();
11526     gameMode = nextGameMode;
11527     ModeHighlight();
11528     endingGame = 0;  /* [HGM] crash */
11529     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11530         if(matchMode == TRUE) { // match through command line: exit with or without popup
11531             if(ranking) {
11532                 ToNrEvent(forwardMostMove);
11533                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11534                 else ExitEvent(0);
11535             } else DisplayFatalError(buf, 0, 0);
11536         } else { // match through menu; just stop, with or without popup
11537             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11538             ModeHighlight();
11539             if(ranking){
11540                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11541             } else DisplayNote(buf);
11542       }
11543       if(ranking) free(ranking);
11544     }
11545 }
11546
11547 /* Assumes program was just initialized (initString sent).
11548    Leaves program in force mode. */
11549 void
11550 FeedMovesToProgram (ChessProgramState *cps, int upto)
11551 {
11552     int i;
11553
11554     if (appData.debugMode)
11555       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11556               startedFromSetupPosition ? "position and " : "",
11557               backwardMostMove, upto, cps->which);
11558     if(currentlyInitializedVariant != gameInfo.variant) {
11559       char buf[MSG_SIZ];
11560         // [HGM] variantswitch: make engine aware of new variant
11561         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11562                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11563                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11564         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11565         SendToProgram(buf, cps);
11566         currentlyInitializedVariant = gameInfo.variant;
11567     }
11568     SendToProgram("force\n", cps);
11569     if (startedFromSetupPosition) {
11570         SendBoard(cps, backwardMostMove);
11571     if (appData.debugMode) {
11572         fprintf(debugFP, "feedMoves\n");
11573     }
11574     }
11575     for (i = backwardMostMove; i < upto; i++) {
11576         SendMoveToProgram(i, cps);
11577     }
11578 }
11579
11580
11581 int
11582 ResurrectChessProgram ()
11583 {
11584      /* The chess program may have exited.
11585         If so, restart it and feed it all the moves made so far. */
11586     static int doInit = 0;
11587
11588     if (appData.noChessProgram) return 1;
11589
11590     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11591         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11592         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11593         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11594     } else {
11595         if (first.pr != NoProc) return 1;
11596         StartChessProgram(&first);
11597     }
11598     InitChessProgram(&first, FALSE);
11599     FeedMovesToProgram(&first, currentMove);
11600
11601     if (!first.sendTime) {
11602         /* can't tell gnuchess what its clock should read,
11603            so we bow to its notion. */
11604         ResetClocks();
11605         timeRemaining[0][currentMove] = whiteTimeRemaining;
11606         timeRemaining[1][currentMove] = blackTimeRemaining;
11607     }
11608
11609     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11610                 appData.icsEngineAnalyze) && first.analysisSupport) {
11611       SendToProgram("analyze\n", &first);
11612       first.analyzing = TRUE;
11613     }
11614     return 1;
11615 }
11616
11617 /*
11618  * Button procedures
11619  */
11620 void
11621 Reset (int redraw, int init)
11622 {
11623     int i;
11624
11625     if (appData.debugMode) {
11626         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11627                 redraw, init, gameMode);
11628     }
11629     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11630     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11631     CleanupTail(); // [HGM] vari: delete any stored variations
11632     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11633     pausing = pauseExamInvalid = FALSE;
11634     startedFromSetupPosition = blackPlaysFirst = FALSE;
11635     firstMove = TRUE;
11636     whiteFlag = blackFlag = FALSE;
11637     userOfferedDraw = FALSE;
11638     hintRequested = bookRequested = FALSE;
11639     first.maybeThinking = FALSE;
11640     second.maybeThinking = FALSE;
11641     first.bookSuspend = FALSE; // [HGM] book
11642     second.bookSuspend = FALSE;
11643     thinkOutput[0] = NULLCHAR;
11644     lastHint[0] = NULLCHAR;
11645     ClearGameInfo(&gameInfo);
11646     gameInfo.variant = StringToVariant(appData.variant);
11647     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11648     ics_user_moved = ics_clock_paused = FALSE;
11649     ics_getting_history = H_FALSE;
11650     ics_gamenum = -1;
11651     white_holding[0] = black_holding[0] = NULLCHAR;
11652     ClearProgramStats();
11653     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11654
11655     ResetFrontEnd();
11656     ClearHighlights();
11657     flipView = appData.flipView;
11658     ClearPremoveHighlights();
11659     gotPremove = FALSE;
11660     alarmSounded = FALSE;
11661     killX = killY = -1; // [HGM] lion
11662
11663     GameEnds(EndOfFile, NULL, GE_PLAYER);
11664     if(appData.serverMovesName != NULL) {
11665         /* [HGM] prepare to make moves file for broadcasting */
11666         clock_t t = clock();
11667         if(serverMoves != NULL) fclose(serverMoves);
11668         serverMoves = fopen(appData.serverMovesName, "r");
11669         if(serverMoves != NULL) {
11670             fclose(serverMoves);
11671             /* delay 15 sec before overwriting, so all clients can see end */
11672             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11673         }
11674         serverMoves = fopen(appData.serverMovesName, "w");
11675     }
11676
11677     ExitAnalyzeMode();
11678     gameMode = BeginningOfGame;
11679     ModeHighlight();
11680     if(appData.icsActive) gameInfo.variant = VariantNormal;
11681     currentMove = forwardMostMove = backwardMostMove = 0;
11682     MarkTargetSquares(1);
11683     InitPosition(redraw);
11684     for (i = 0; i < MAX_MOVES; i++) {
11685         if (commentList[i] != NULL) {
11686             free(commentList[i]);
11687             commentList[i] = NULL;
11688         }
11689     }
11690     ResetClocks();
11691     timeRemaining[0][0] = whiteTimeRemaining;
11692     timeRemaining[1][0] = blackTimeRemaining;
11693
11694     if (first.pr == NoProc) {
11695         StartChessProgram(&first);
11696     }
11697     if (init) {
11698             InitChessProgram(&first, startedFromSetupPosition);
11699     }
11700     DisplayTitle("");
11701     DisplayMessage("", "");
11702     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11703     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11704     ClearMap();        // [HGM] exclude: invalidate map
11705 }
11706
11707 void
11708 AutoPlayGameLoop ()
11709 {
11710     for (;;) {
11711         if (!AutoPlayOneMove())
11712           return;
11713         if (matchMode || appData.timeDelay == 0)
11714           continue;
11715         if (appData.timeDelay < 0)
11716           return;
11717         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11718         break;
11719     }
11720 }
11721
11722 void
11723 AnalyzeNextGame()
11724 {
11725     ReloadGame(1); // next game
11726 }
11727
11728 int
11729 AutoPlayOneMove ()
11730 {
11731     int fromX, fromY, toX, toY;
11732
11733     if (appData.debugMode) {
11734       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11735     }
11736
11737     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11738       return FALSE;
11739
11740     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11741       pvInfoList[currentMove].depth = programStats.depth;
11742       pvInfoList[currentMove].score = programStats.score;
11743       pvInfoList[currentMove].time  = 0;
11744       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11745       else { // append analysis of final position as comment
11746         char buf[MSG_SIZ];
11747         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11748         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11749       }
11750       programStats.depth = 0;
11751     }
11752
11753     if (currentMove >= forwardMostMove) {
11754       if(gameMode == AnalyzeFile) {
11755           if(appData.loadGameIndex == -1) {
11756             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11757           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11758           } else {
11759           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11760         }
11761       }
11762 //      gameMode = EndOfGame;
11763 //      ModeHighlight();
11764
11765       /* [AS] Clear current move marker at the end of a game */
11766       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11767
11768       return FALSE;
11769     }
11770
11771     toX = moveList[currentMove][2] - AAA;
11772     toY = moveList[currentMove][3] - ONE;
11773
11774     if (moveList[currentMove][1] == '@') {
11775         if (appData.highlightLastMove) {
11776             SetHighlights(-1, -1, toX, toY);
11777         }
11778     } else {
11779         int viaX = moveList[currentMove][5] - AAA;
11780         int viaY = moveList[currentMove][6] - ONE;
11781         fromX = moveList[currentMove][0] - AAA;
11782         fromY = moveList[currentMove][1] - ONE;
11783
11784         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11785
11786         if(moveList[currentMove][4] == ';') { // multi-leg
11787             ChessSquare piece = boards[currentMove][viaY][viaX];
11788             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11789             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11790             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11791             boards[currentMove][viaY][viaX] = piece;
11792         } else
11793         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11794
11795         if (appData.highlightLastMove) {
11796             SetHighlights(fromX, fromY, toX, toY);
11797         }
11798     }
11799     DisplayMove(currentMove);
11800     SendMoveToProgram(currentMove++, &first);
11801     DisplayBothClocks();
11802     DrawPosition(FALSE, boards[currentMove]);
11803     // [HGM] PV info: always display, routine tests if empty
11804     DisplayComment(currentMove - 1, commentList[currentMove]);
11805     return TRUE;
11806 }
11807
11808
11809 int
11810 LoadGameOneMove (ChessMove readAhead)
11811 {
11812     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11813     char promoChar = NULLCHAR;
11814     ChessMove moveType;
11815     char move[MSG_SIZ];
11816     char *p, *q;
11817
11818     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11819         gameMode != AnalyzeMode && gameMode != Training) {
11820         gameFileFP = NULL;
11821         return FALSE;
11822     }
11823
11824     yyboardindex = forwardMostMove;
11825     if (readAhead != EndOfFile) {
11826       moveType = readAhead;
11827     } else {
11828       if (gameFileFP == NULL)
11829           return FALSE;
11830       moveType = (ChessMove) Myylex();
11831     }
11832
11833     done = FALSE;
11834     switch (moveType) {
11835       case Comment:
11836         if (appData.debugMode)
11837           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11838         p = yy_text;
11839
11840         /* append the comment but don't display it */
11841         AppendComment(currentMove, p, FALSE);
11842         return TRUE;
11843
11844       case WhiteCapturesEnPassant:
11845       case BlackCapturesEnPassant:
11846       case WhitePromotion:
11847       case BlackPromotion:
11848       case WhiteNonPromotion:
11849       case BlackNonPromotion:
11850       case NormalMove:
11851       case FirstLeg:
11852       case WhiteKingSideCastle:
11853       case WhiteQueenSideCastle:
11854       case BlackKingSideCastle:
11855       case BlackQueenSideCastle:
11856       case WhiteKingSideCastleWild:
11857       case WhiteQueenSideCastleWild:
11858       case BlackKingSideCastleWild:
11859       case BlackQueenSideCastleWild:
11860       /* PUSH Fabien */
11861       case WhiteHSideCastleFR:
11862       case WhiteASideCastleFR:
11863       case BlackHSideCastleFR:
11864       case BlackASideCastleFR:
11865       /* POP Fabien */
11866         if (appData.debugMode)
11867           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11868         fromX = currentMoveString[0] - AAA;
11869         fromY = currentMoveString[1] - ONE;
11870         toX = currentMoveString[2] - AAA;
11871         toY = currentMoveString[3] - ONE;
11872         promoChar = currentMoveString[4];
11873         if(promoChar == ';') promoChar = NULLCHAR;
11874         break;
11875
11876       case WhiteDrop:
11877       case BlackDrop:
11878         if (appData.debugMode)
11879           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11880         fromX = moveType == WhiteDrop ?
11881           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11882         (int) CharToPiece(ToLower(currentMoveString[0]));
11883         fromY = DROP_RANK;
11884         toX = currentMoveString[2] - AAA;
11885         toY = currentMoveString[3] - ONE;
11886         break;
11887
11888       case WhiteWins:
11889       case BlackWins:
11890       case GameIsDrawn:
11891       case GameUnfinished:
11892         if (appData.debugMode)
11893           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11894         p = strchr(yy_text, '{');
11895         if (p == NULL) p = strchr(yy_text, '(');
11896         if (p == NULL) {
11897             p = yy_text;
11898             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11899         } else {
11900             q = strchr(p, *p == '{' ? '}' : ')');
11901             if (q != NULL) *q = NULLCHAR;
11902             p++;
11903         }
11904         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11905         GameEnds(moveType, p, GE_FILE);
11906         done = TRUE;
11907         if (cmailMsgLoaded) {
11908             ClearHighlights();
11909             flipView = WhiteOnMove(currentMove);
11910             if (moveType == GameUnfinished) flipView = !flipView;
11911             if (appData.debugMode)
11912               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11913         }
11914         break;
11915
11916       case EndOfFile:
11917         if (appData.debugMode)
11918           fprintf(debugFP, "Parser hit end of file\n");
11919         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11920           case MT_NONE:
11921           case MT_CHECK:
11922             break;
11923           case MT_CHECKMATE:
11924           case MT_STAINMATE:
11925             if (WhiteOnMove(currentMove)) {
11926                 GameEnds(BlackWins, "Black mates", GE_FILE);
11927             } else {
11928                 GameEnds(WhiteWins, "White mates", GE_FILE);
11929             }
11930             break;
11931           case MT_STALEMATE:
11932             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11933             break;
11934         }
11935         done = TRUE;
11936         break;
11937
11938       case MoveNumberOne:
11939         if (lastLoadGameStart == GNUChessGame) {
11940             /* GNUChessGames have numbers, but they aren't move numbers */
11941             if (appData.debugMode)
11942               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11943                       yy_text, (int) moveType);
11944             return LoadGameOneMove(EndOfFile); /* tail recursion */
11945         }
11946         /* else fall thru */
11947
11948       case XBoardGame:
11949       case GNUChessGame:
11950       case PGNTag:
11951         /* Reached start of next game in file */
11952         if (appData.debugMode)
11953           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11954         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11955           case MT_NONE:
11956           case MT_CHECK:
11957             break;
11958           case MT_CHECKMATE:
11959           case MT_STAINMATE:
11960             if (WhiteOnMove(currentMove)) {
11961                 GameEnds(BlackWins, "Black mates", GE_FILE);
11962             } else {
11963                 GameEnds(WhiteWins, "White mates", GE_FILE);
11964             }
11965             break;
11966           case MT_STALEMATE:
11967             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11968             break;
11969         }
11970         done = TRUE;
11971         break;
11972
11973       case PositionDiagram:     /* should not happen; ignore */
11974       case ElapsedTime:         /* ignore */
11975       case NAG:                 /* ignore */
11976         if (appData.debugMode)
11977           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11978                   yy_text, (int) moveType);
11979         return LoadGameOneMove(EndOfFile); /* tail recursion */
11980
11981       case IllegalMove:
11982         if (appData.testLegality) {
11983             if (appData.debugMode)
11984               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11985             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11986                     (forwardMostMove / 2) + 1,
11987                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11988             DisplayError(move, 0);
11989             done = TRUE;
11990         } else {
11991             if (appData.debugMode)
11992               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11993                       yy_text, currentMoveString);
11994             fromX = currentMoveString[0] - AAA;
11995             fromY = currentMoveString[1] - ONE;
11996             toX = currentMoveString[2] - AAA;
11997             toY = currentMoveString[3] - ONE;
11998             promoChar = currentMoveString[4];
11999         }
12000         break;
12001
12002       case AmbiguousMove:
12003         if (appData.debugMode)
12004           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12005         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12006                 (forwardMostMove / 2) + 1,
12007                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12008         DisplayError(move, 0);
12009         done = TRUE;
12010         break;
12011
12012       default:
12013       case ImpossibleMove:
12014         if (appData.debugMode)
12015           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12016         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12017                 (forwardMostMove / 2) + 1,
12018                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12019         DisplayError(move, 0);
12020         done = TRUE;
12021         break;
12022     }
12023
12024     if (done) {
12025         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12026             DrawPosition(FALSE, boards[currentMove]);
12027             DisplayBothClocks();
12028             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12029               DisplayComment(currentMove - 1, commentList[currentMove]);
12030         }
12031         (void) StopLoadGameTimer();
12032         gameFileFP = NULL;
12033         cmailOldMove = forwardMostMove;
12034         return FALSE;
12035     } else {
12036         /* currentMoveString is set as a side-effect of yylex */
12037
12038         thinkOutput[0] = NULLCHAR;
12039         MakeMove(fromX, fromY, toX, toY, promoChar);
12040         killX = killY = -1; // [HGM] lion: used up
12041         currentMove = forwardMostMove;
12042         return TRUE;
12043     }
12044 }
12045
12046 /* Load the nth game from the given file */
12047 int
12048 LoadGameFromFile (char *filename, int n, char *title, int useList)
12049 {
12050     FILE *f;
12051     char buf[MSG_SIZ];
12052
12053     if (strcmp(filename, "-") == 0) {
12054         f = stdin;
12055         title = "stdin";
12056     } else {
12057         f = fopen(filename, "rb");
12058         if (f == NULL) {
12059           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12060             DisplayError(buf, errno);
12061             return FALSE;
12062         }
12063     }
12064     if (fseek(f, 0, 0) == -1) {
12065         /* f is not seekable; probably a pipe */
12066         useList = FALSE;
12067     }
12068     if (useList && n == 0) {
12069         int error = GameListBuild(f);
12070         if (error) {
12071             DisplayError(_("Cannot build game list"), error);
12072         } else if (!ListEmpty(&gameList) &&
12073                    ((ListGame *) gameList.tailPred)->number > 1) {
12074             GameListPopUp(f, title);
12075             return TRUE;
12076         }
12077         GameListDestroy();
12078         n = 1;
12079     }
12080     if (n == 0) n = 1;
12081     return LoadGame(f, n, title, FALSE);
12082 }
12083
12084
12085 void
12086 MakeRegisteredMove ()
12087 {
12088     int fromX, fromY, toX, toY;
12089     char promoChar;
12090     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12091         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12092           case CMAIL_MOVE:
12093           case CMAIL_DRAW:
12094             if (appData.debugMode)
12095               fprintf(debugFP, "Restoring %s for game %d\n",
12096                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12097
12098             thinkOutput[0] = NULLCHAR;
12099             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12100             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12101             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12102             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12103             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12104             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12105             MakeMove(fromX, fromY, toX, toY, promoChar);
12106             ShowMove(fromX, fromY, toX, toY);
12107
12108             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12109               case MT_NONE:
12110               case MT_CHECK:
12111                 break;
12112
12113               case MT_CHECKMATE:
12114               case MT_STAINMATE:
12115                 if (WhiteOnMove(currentMove)) {
12116                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12117                 } else {
12118                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12119                 }
12120                 break;
12121
12122               case MT_STALEMATE:
12123                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12124                 break;
12125             }
12126
12127             break;
12128
12129           case CMAIL_RESIGN:
12130             if (WhiteOnMove(currentMove)) {
12131                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12132             } else {
12133                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12134             }
12135             break;
12136
12137           case CMAIL_ACCEPT:
12138             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12139             break;
12140
12141           default:
12142             break;
12143         }
12144     }
12145
12146     return;
12147 }
12148
12149 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12150 int
12151 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12152 {
12153     int retVal;
12154
12155     if (gameNumber > nCmailGames) {
12156         DisplayError(_("No more games in this message"), 0);
12157         return FALSE;
12158     }
12159     if (f == lastLoadGameFP) {
12160         int offset = gameNumber - lastLoadGameNumber;
12161         if (offset == 0) {
12162             cmailMsg[0] = NULLCHAR;
12163             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12164                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12165                 nCmailMovesRegistered--;
12166             }
12167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12168             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12169                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12170             }
12171         } else {
12172             if (! RegisterMove()) return FALSE;
12173         }
12174     }
12175
12176     retVal = LoadGame(f, gameNumber, title, useList);
12177
12178     /* Make move registered during previous look at this game, if any */
12179     MakeRegisteredMove();
12180
12181     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12182         commentList[currentMove]
12183           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12184         DisplayComment(currentMove - 1, commentList[currentMove]);
12185     }
12186
12187     return retVal;
12188 }
12189
12190 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12191 int
12192 ReloadGame (int offset)
12193 {
12194     int gameNumber = lastLoadGameNumber + offset;
12195     if (lastLoadGameFP == NULL) {
12196         DisplayError(_("No game has been loaded yet"), 0);
12197         return FALSE;
12198     }
12199     if (gameNumber <= 0) {
12200         DisplayError(_("Can't back up any further"), 0);
12201         return FALSE;
12202     }
12203     if (cmailMsgLoaded) {
12204         return CmailLoadGame(lastLoadGameFP, gameNumber,
12205                              lastLoadGameTitle, lastLoadGameUseList);
12206     } else {
12207         return LoadGame(lastLoadGameFP, gameNumber,
12208                         lastLoadGameTitle, lastLoadGameUseList);
12209     }
12210 }
12211
12212 int keys[EmptySquare+1];
12213
12214 int
12215 PositionMatches (Board b1, Board b2)
12216 {
12217     int r, f, sum=0;
12218     switch(appData.searchMode) {
12219         case 1: return CompareWithRights(b1, b2);
12220         case 2:
12221             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12222                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12223             }
12224             return TRUE;
12225         case 3:
12226             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12227               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12228                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12229             }
12230             return sum==0;
12231         case 4:
12232             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12233                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12234             }
12235             return sum==0;
12236     }
12237     return TRUE;
12238 }
12239
12240 #define Q_PROMO  4
12241 #define Q_EP     3
12242 #define Q_BCASTL 2
12243 #define Q_WCASTL 1
12244
12245 int pieceList[256], quickBoard[256];
12246 ChessSquare pieceType[256] = { EmptySquare };
12247 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12248 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12249 int soughtTotal, turn;
12250 Boolean epOK, flipSearch;
12251
12252 typedef struct {
12253     unsigned char piece, to;
12254 } Move;
12255
12256 #define DSIZE (250000)
12257
12258 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12259 Move *moveDatabase = initialSpace;
12260 unsigned int movePtr, dataSize = DSIZE;
12261
12262 int
12263 MakePieceList (Board board, int *counts)
12264 {
12265     int r, f, n=Q_PROMO, total=0;
12266     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12267     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12268         int sq = f + (r<<4);
12269         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12270             quickBoard[sq] = ++n;
12271             pieceList[n] = sq;
12272             pieceType[n] = board[r][f];
12273             counts[board[r][f]]++;
12274             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12275             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12276             total++;
12277         }
12278     }
12279     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12280     return total;
12281 }
12282
12283 void
12284 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12285 {
12286     int sq = fromX + (fromY<<4);
12287     int piece = quickBoard[sq], rook;
12288     quickBoard[sq] = 0;
12289     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12290     if(piece == pieceList[1] && fromY == toY) {
12291       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12292         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12293         moveDatabase[movePtr++].piece = Q_WCASTL;
12294         quickBoard[sq] = piece;
12295         piece = quickBoard[from]; quickBoard[from] = 0;
12296         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12297       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12298         quickBoard[sq] = 0; // remove Rook
12299         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12300         moveDatabase[movePtr++].piece = Q_WCASTL;
12301         quickBoard[sq] = pieceList[1]; // put King
12302         piece = rook;
12303         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12304       }
12305     } else
12306     if(piece == pieceList[2] && fromY == toY) {
12307       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12308         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12309         moveDatabase[movePtr++].piece = Q_BCASTL;
12310         quickBoard[sq] = piece;
12311         piece = quickBoard[from]; quickBoard[from] = 0;
12312         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12313       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12314         quickBoard[sq] = 0; // remove Rook
12315         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12316         moveDatabase[movePtr++].piece = Q_BCASTL;
12317         quickBoard[sq] = pieceList[2]; // put King
12318         piece = rook;
12319         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12320       }
12321     } else
12322     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12323         quickBoard[(fromY<<4)+toX] = 0;
12324         moveDatabase[movePtr].piece = Q_EP;
12325         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12326         moveDatabase[movePtr].to = sq;
12327     } else
12328     if(promoPiece != pieceType[piece]) {
12329         moveDatabase[movePtr++].piece = Q_PROMO;
12330         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12331     }
12332     moveDatabase[movePtr].piece = piece;
12333     quickBoard[sq] = piece;
12334     movePtr++;
12335 }
12336
12337 int
12338 PackGame (Board board)
12339 {
12340     Move *newSpace = NULL;
12341     moveDatabase[movePtr].piece = 0; // terminate previous game
12342     if(movePtr > dataSize) {
12343         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12344         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12345         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12346         if(newSpace) {
12347             int i;
12348             Move *p = moveDatabase, *q = newSpace;
12349             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12350             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12351             moveDatabase = newSpace;
12352         } else { // calloc failed, we must be out of memory. Too bad...
12353             dataSize = 0; // prevent calloc events for all subsequent games
12354             return 0;     // and signal this one isn't cached
12355         }
12356     }
12357     movePtr++;
12358     MakePieceList(board, counts);
12359     return movePtr;
12360 }
12361
12362 int
12363 QuickCompare (Board board, int *minCounts, int *maxCounts)
12364 {   // compare according to search mode
12365     int r, f;
12366     switch(appData.searchMode)
12367     {
12368       case 1: // exact position match
12369         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12370         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12371             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12372         }
12373         break;
12374       case 2: // can have extra material on empty squares
12375         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12376             if(board[r][f] == EmptySquare) continue;
12377             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12378         }
12379         break;
12380       case 3: // material with exact Pawn structure
12381         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12382             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12383             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12384         } // fall through to material comparison
12385       case 4: // exact material
12386         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12387         break;
12388       case 6: // material range with given imbalance
12389         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12390         // fall through to range comparison
12391       case 5: // material range
12392         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12393     }
12394     return TRUE;
12395 }
12396
12397 int
12398 QuickScan (Board board, Move *move)
12399 {   // reconstruct game,and compare all positions in it
12400     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12401     do {
12402         int piece = move->piece;
12403         int to = move->to, from = pieceList[piece];
12404         if(found < 0) { // if already found just scan to game end for final piece count
12405           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12406            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12407            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12408                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12409             ) {
12410             static int lastCounts[EmptySquare+1];
12411             int i;
12412             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12413             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12414           } else stretch = 0;
12415           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12416           if(found >= 0 && !appData.minPieces) return found;
12417         }
12418         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12419           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12420           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12421             piece = (++move)->piece;
12422             from = pieceList[piece];
12423             counts[pieceType[piece]]--;
12424             pieceType[piece] = (ChessSquare) move->to;
12425             counts[move->to]++;
12426           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12427             counts[pieceType[quickBoard[to]]]--;
12428             quickBoard[to] = 0; total--;
12429             move++;
12430             continue;
12431           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12432             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12433             from  = pieceList[piece]; // so this must be King
12434             quickBoard[from] = 0;
12435             pieceList[piece] = to;
12436             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12437             quickBoard[from] = 0; // rook
12438             quickBoard[to] = piece;
12439             to = move->to; piece = move->piece;
12440             goto aftercastle;
12441           }
12442         }
12443         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12444         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12445         quickBoard[from] = 0;
12446       aftercastle:
12447         quickBoard[to] = piece;
12448         pieceList[piece] = to;
12449         cnt++; turn ^= 3;
12450         move++;
12451     } while(1);
12452 }
12453
12454 void
12455 InitSearch ()
12456 {
12457     int r, f;
12458     flipSearch = FALSE;
12459     CopyBoard(soughtBoard, boards[currentMove]);
12460     soughtTotal = MakePieceList(soughtBoard, maxSought);
12461     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12462     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12463     CopyBoard(reverseBoard, boards[currentMove]);
12464     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12465         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12466         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12467         reverseBoard[r][f] = piece;
12468     }
12469     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12470     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12471     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12472                  || (boards[currentMove][CASTLING][2] == NoRights ||
12473                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12474                  && (boards[currentMove][CASTLING][5] == NoRights ||
12475                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12476       ) {
12477         flipSearch = TRUE;
12478         CopyBoard(flipBoard, soughtBoard);
12479         CopyBoard(rotateBoard, reverseBoard);
12480         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12481             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12482             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12483         }
12484     }
12485     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12486     if(appData.searchMode >= 5) {
12487         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12488         MakePieceList(soughtBoard, minSought);
12489         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12490     }
12491     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12492         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12493 }
12494
12495 GameInfo dummyInfo;
12496 static int creatingBook;
12497
12498 int
12499 GameContainsPosition (FILE *f, ListGame *lg)
12500 {
12501     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12502     int fromX, fromY, toX, toY;
12503     char promoChar;
12504     static int initDone=FALSE;
12505
12506     // weed out games based on numerical tag comparison
12507     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12508     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12509     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12510     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12511     if(!initDone) {
12512         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12513         initDone = TRUE;
12514     }
12515     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12516     else CopyBoard(boards[scratch], initialPosition); // default start position
12517     if(lg->moves) {
12518         turn = btm + 1;
12519         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12520         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12521     }
12522     if(btm) plyNr++;
12523     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12524     fseek(f, lg->offset, 0);
12525     yynewfile(f);
12526     while(1) {
12527         yyboardindex = scratch;
12528         quickFlag = plyNr+1;
12529         next = Myylex();
12530         quickFlag = 0;
12531         switch(next) {
12532             case PGNTag:
12533                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12534             default:
12535                 continue;
12536
12537             case XBoardGame:
12538             case GNUChessGame:
12539                 if(plyNr) return -1; // after we have seen moves, this is for new game
12540               continue;
12541
12542             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12543             case ImpossibleMove:
12544             case WhiteWins: // game ends here with these four
12545             case BlackWins:
12546             case GameIsDrawn:
12547             case GameUnfinished:
12548                 return -1;
12549
12550             case IllegalMove:
12551                 if(appData.testLegality) return -1;
12552             case WhiteCapturesEnPassant:
12553             case BlackCapturesEnPassant:
12554             case WhitePromotion:
12555             case BlackPromotion:
12556             case WhiteNonPromotion:
12557             case BlackNonPromotion:
12558             case NormalMove:
12559             case FirstLeg:
12560             case WhiteKingSideCastle:
12561             case WhiteQueenSideCastle:
12562             case BlackKingSideCastle:
12563             case BlackQueenSideCastle:
12564             case WhiteKingSideCastleWild:
12565             case WhiteQueenSideCastleWild:
12566             case BlackKingSideCastleWild:
12567             case BlackQueenSideCastleWild:
12568             case WhiteHSideCastleFR:
12569             case WhiteASideCastleFR:
12570             case BlackHSideCastleFR:
12571             case BlackASideCastleFR:
12572                 fromX = currentMoveString[0] - AAA;
12573                 fromY = currentMoveString[1] - ONE;
12574                 toX = currentMoveString[2] - AAA;
12575                 toY = currentMoveString[3] - ONE;
12576                 promoChar = currentMoveString[4];
12577                 break;
12578             case WhiteDrop:
12579             case BlackDrop:
12580                 fromX = next == WhiteDrop ?
12581                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12582                   (int) CharToPiece(ToLower(currentMoveString[0]));
12583                 fromY = DROP_RANK;
12584                 toX = currentMoveString[2] - AAA;
12585                 toY = currentMoveString[3] - ONE;
12586                 promoChar = 0;
12587                 break;
12588         }
12589         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12590         plyNr++;
12591         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12592         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12593         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12594         if(appData.findMirror) {
12595             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12596             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12597         }
12598     }
12599 }
12600
12601 /* Load the nth game from open file f */
12602 int
12603 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12604 {
12605     ChessMove cm;
12606     char buf[MSG_SIZ];
12607     int gn = gameNumber;
12608     ListGame *lg = NULL;
12609     int numPGNTags = 0;
12610     int err, pos = -1;
12611     GameMode oldGameMode;
12612     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12613
12614     if (appData.debugMode)
12615         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12616
12617     if (gameMode == Training )
12618         SetTrainingModeOff();
12619
12620     oldGameMode = gameMode;
12621     if (gameMode != BeginningOfGame) {
12622       Reset(FALSE, TRUE);
12623     }
12624     killX = killY = -1; // [HGM] lion: in case we did not Reset
12625
12626     gameFileFP = f;
12627     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12628         fclose(lastLoadGameFP);
12629     }
12630
12631     if (useList) {
12632         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12633
12634         if (lg) {
12635             fseek(f, lg->offset, 0);
12636             GameListHighlight(gameNumber);
12637             pos = lg->position;
12638             gn = 1;
12639         }
12640         else {
12641             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12642               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12643             else
12644             DisplayError(_("Game number out of range"), 0);
12645             return FALSE;
12646         }
12647     } else {
12648         GameListDestroy();
12649         if (fseek(f, 0, 0) == -1) {
12650             if (f == lastLoadGameFP ?
12651                 gameNumber == lastLoadGameNumber + 1 :
12652                 gameNumber == 1) {
12653                 gn = 1;
12654             } else {
12655                 DisplayError(_("Can't seek on game file"), 0);
12656                 return FALSE;
12657             }
12658         }
12659     }
12660     lastLoadGameFP = f;
12661     lastLoadGameNumber = gameNumber;
12662     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12663     lastLoadGameUseList = useList;
12664
12665     yynewfile(f);
12666
12667     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12668       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12669                 lg->gameInfo.black);
12670             DisplayTitle(buf);
12671     } else if (*title != NULLCHAR) {
12672         if (gameNumber > 1) {
12673           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12674             DisplayTitle(buf);
12675         } else {
12676             DisplayTitle(title);
12677         }
12678     }
12679
12680     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12681         gameMode = PlayFromGameFile;
12682         ModeHighlight();
12683     }
12684
12685     currentMove = forwardMostMove = backwardMostMove = 0;
12686     CopyBoard(boards[0], initialPosition);
12687     StopClocks();
12688
12689     /*
12690      * Skip the first gn-1 games in the file.
12691      * Also skip over anything that precedes an identifiable
12692      * start of game marker, to avoid being confused by
12693      * garbage at the start of the file.  Currently
12694      * recognized start of game markers are the move number "1",
12695      * the pattern "gnuchess .* game", the pattern
12696      * "^[#;%] [^ ]* game file", and a PGN tag block.
12697      * A game that starts with one of the latter two patterns
12698      * will also have a move number 1, possibly
12699      * following a position diagram.
12700      * 5-4-02: Let's try being more lenient and allowing a game to
12701      * start with an unnumbered move.  Does that break anything?
12702      */
12703     cm = lastLoadGameStart = EndOfFile;
12704     while (gn > 0) {
12705         yyboardindex = forwardMostMove;
12706         cm = (ChessMove) Myylex();
12707         switch (cm) {
12708           case EndOfFile:
12709             if (cmailMsgLoaded) {
12710                 nCmailGames = CMAIL_MAX_GAMES - gn;
12711             } else {
12712                 Reset(TRUE, TRUE);
12713                 DisplayError(_("Game not found in file"), 0);
12714             }
12715             return FALSE;
12716
12717           case GNUChessGame:
12718           case XBoardGame:
12719             gn--;
12720             lastLoadGameStart = cm;
12721             break;
12722
12723           case MoveNumberOne:
12724             switch (lastLoadGameStart) {
12725               case GNUChessGame:
12726               case XBoardGame:
12727               case PGNTag:
12728                 break;
12729               case MoveNumberOne:
12730               case EndOfFile:
12731                 gn--;           /* count this game */
12732                 lastLoadGameStart = cm;
12733                 break;
12734               default:
12735                 /* impossible */
12736                 break;
12737             }
12738             break;
12739
12740           case PGNTag:
12741             switch (lastLoadGameStart) {
12742               case GNUChessGame:
12743               case PGNTag:
12744               case MoveNumberOne:
12745               case EndOfFile:
12746                 gn--;           /* count this game */
12747                 lastLoadGameStart = cm;
12748                 break;
12749               case XBoardGame:
12750                 lastLoadGameStart = cm; /* game counted already */
12751                 break;
12752               default:
12753                 /* impossible */
12754                 break;
12755             }
12756             if (gn > 0) {
12757                 do {
12758                     yyboardindex = forwardMostMove;
12759                     cm = (ChessMove) Myylex();
12760                 } while (cm == PGNTag || cm == Comment);
12761             }
12762             break;
12763
12764           case WhiteWins:
12765           case BlackWins:
12766           case GameIsDrawn:
12767             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12768                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12769                     != CMAIL_OLD_RESULT) {
12770                     nCmailResults ++ ;
12771                     cmailResult[  CMAIL_MAX_GAMES
12772                                 - gn - 1] = CMAIL_OLD_RESULT;
12773                 }
12774             }
12775             break;
12776
12777           case NormalMove:
12778           case FirstLeg:
12779             /* Only a NormalMove can be at the start of a game
12780              * without a position diagram. */
12781             if (lastLoadGameStart == EndOfFile ) {
12782               gn--;
12783               lastLoadGameStart = MoveNumberOne;
12784             }
12785             break;
12786
12787           default:
12788             break;
12789         }
12790     }
12791
12792     if (appData.debugMode)
12793       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12794
12795     if (cm == XBoardGame) {
12796         /* Skip any header junk before position diagram and/or move 1 */
12797         for (;;) {
12798             yyboardindex = forwardMostMove;
12799             cm = (ChessMove) Myylex();
12800
12801             if (cm == EndOfFile ||
12802                 cm == GNUChessGame || cm == XBoardGame) {
12803                 /* Empty game; pretend end-of-file and handle later */
12804                 cm = EndOfFile;
12805                 break;
12806             }
12807
12808             if (cm == MoveNumberOne || cm == PositionDiagram ||
12809                 cm == PGNTag || cm == Comment)
12810               break;
12811         }
12812     } else if (cm == GNUChessGame) {
12813         if (gameInfo.event != NULL) {
12814             free(gameInfo.event);
12815         }
12816         gameInfo.event = StrSave(yy_text);
12817     }
12818
12819     startedFromSetupPosition = FALSE;
12820     while (cm == PGNTag) {
12821         if (appData.debugMode)
12822           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12823         err = ParsePGNTag(yy_text, &gameInfo);
12824         if (!err) numPGNTags++;
12825
12826         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12827         if(gameInfo.variant != oldVariant) {
12828             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12829             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12830             InitPosition(TRUE);
12831             oldVariant = gameInfo.variant;
12832             if (appData.debugMode)
12833               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12834         }
12835
12836
12837         if (gameInfo.fen != NULL) {
12838           Board initial_position;
12839           startedFromSetupPosition = TRUE;
12840           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12841             Reset(TRUE, TRUE);
12842             DisplayError(_("Bad FEN position in file"), 0);
12843             return FALSE;
12844           }
12845           CopyBoard(boards[0], initial_position);
12846           if (blackPlaysFirst) {
12847             currentMove = forwardMostMove = backwardMostMove = 1;
12848             CopyBoard(boards[1], initial_position);
12849             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12850             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12851             timeRemaining[0][1] = whiteTimeRemaining;
12852             timeRemaining[1][1] = blackTimeRemaining;
12853             if (commentList[0] != NULL) {
12854               commentList[1] = commentList[0];
12855               commentList[0] = NULL;
12856             }
12857           } else {
12858             currentMove = forwardMostMove = backwardMostMove = 0;
12859           }
12860           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12861           {   int i;
12862               initialRulePlies = FENrulePlies;
12863               for( i=0; i< nrCastlingRights; i++ )
12864                   initialRights[i] = initial_position[CASTLING][i];
12865           }
12866           yyboardindex = forwardMostMove;
12867           free(gameInfo.fen);
12868           gameInfo.fen = NULL;
12869         }
12870
12871         yyboardindex = forwardMostMove;
12872         cm = (ChessMove) Myylex();
12873
12874         /* Handle comments interspersed among the tags */
12875         while (cm == Comment) {
12876             char *p;
12877             if (appData.debugMode)
12878               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12879             p = yy_text;
12880             AppendComment(currentMove, p, FALSE);
12881             yyboardindex = forwardMostMove;
12882             cm = (ChessMove) Myylex();
12883         }
12884     }
12885
12886     /* don't rely on existence of Event tag since if game was
12887      * pasted from clipboard the Event tag may not exist
12888      */
12889     if (numPGNTags > 0){
12890         char *tags;
12891         if (gameInfo.variant == VariantNormal) {
12892           VariantClass v = StringToVariant(gameInfo.event);
12893           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12894           if(v < VariantShogi) gameInfo.variant = v;
12895         }
12896         if (!matchMode) {
12897           if( appData.autoDisplayTags ) {
12898             tags = PGNTags(&gameInfo);
12899             TagsPopUp(tags, CmailMsg());
12900             free(tags);
12901           }
12902         }
12903     } else {
12904         /* Make something up, but don't display it now */
12905         SetGameInfo();
12906         TagsPopDown();
12907     }
12908
12909     if (cm == PositionDiagram) {
12910         int i, j;
12911         char *p;
12912         Board initial_position;
12913
12914         if (appData.debugMode)
12915           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12916
12917         if (!startedFromSetupPosition) {
12918             p = yy_text;
12919             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12920               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12921                 switch (*p) {
12922                   case '{':
12923                   case '[':
12924                   case '-':
12925                   case ' ':
12926                   case '\t':
12927                   case '\n':
12928                   case '\r':
12929                     break;
12930                   default:
12931                     initial_position[i][j++] = CharToPiece(*p);
12932                     break;
12933                 }
12934             while (*p == ' ' || *p == '\t' ||
12935                    *p == '\n' || *p == '\r') p++;
12936
12937             if (strncmp(p, "black", strlen("black"))==0)
12938               blackPlaysFirst = TRUE;
12939             else
12940               blackPlaysFirst = FALSE;
12941             startedFromSetupPosition = TRUE;
12942
12943             CopyBoard(boards[0], initial_position);
12944             if (blackPlaysFirst) {
12945                 currentMove = forwardMostMove = backwardMostMove = 1;
12946                 CopyBoard(boards[1], initial_position);
12947                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12948                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12949                 timeRemaining[0][1] = whiteTimeRemaining;
12950                 timeRemaining[1][1] = blackTimeRemaining;
12951                 if (commentList[0] != NULL) {
12952                     commentList[1] = commentList[0];
12953                     commentList[0] = NULL;
12954                 }
12955             } else {
12956                 currentMove = forwardMostMove = backwardMostMove = 0;
12957             }
12958         }
12959         yyboardindex = forwardMostMove;
12960         cm = (ChessMove) Myylex();
12961     }
12962
12963   if(!creatingBook) {
12964     if (first.pr == NoProc) {
12965         StartChessProgram(&first);
12966     }
12967     InitChessProgram(&first, FALSE);
12968     SendToProgram("force\n", &first);
12969     if (startedFromSetupPosition) {
12970         SendBoard(&first, forwardMostMove);
12971     if (appData.debugMode) {
12972         fprintf(debugFP, "Load Game\n");
12973     }
12974         DisplayBothClocks();
12975     }
12976   }
12977
12978     /* [HGM] server: flag to write setup moves in broadcast file as one */
12979     loadFlag = appData.suppressLoadMoves;
12980
12981     while (cm == Comment) {
12982         char *p;
12983         if (appData.debugMode)
12984           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12985         p = yy_text;
12986         AppendComment(currentMove, p, FALSE);
12987         yyboardindex = forwardMostMove;
12988         cm = (ChessMove) Myylex();
12989     }
12990
12991     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12992         cm == WhiteWins || cm == BlackWins ||
12993         cm == GameIsDrawn || cm == GameUnfinished) {
12994         DisplayMessage("", _("No moves in game"));
12995         if (cmailMsgLoaded) {
12996             if (appData.debugMode)
12997               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12998             ClearHighlights();
12999             flipView = FALSE;
13000         }
13001         DrawPosition(FALSE, boards[currentMove]);
13002         DisplayBothClocks();
13003         gameMode = EditGame;
13004         ModeHighlight();
13005         gameFileFP = NULL;
13006         cmailOldMove = 0;
13007         return TRUE;
13008     }
13009
13010     // [HGM] PV info: routine tests if comment empty
13011     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13012         DisplayComment(currentMove - 1, commentList[currentMove]);
13013     }
13014     if (!matchMode && appData.timeDelay != 0)
13015       DrawPosition(FALSE, boards[currentMove]);
13016
13017     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13018       programStats.ok_to_send = 1;
13019     }
13020
13021     /* if the first token after the PGN tags is a move
13022      * and not move number 1, retrieve it from the parser
13023      */
13024     if (cm != MoveNumberOne)
13025         LoadGameOneMove(cm);
13026
13027     /* load the remaining moves from the file */
13028     while (LoadGameOneMove(EndOfFile)) {
13029       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13030       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13031     }
13032
13033     /* rewind to the start of the game */
13034     currentMove = backwardMostMove;
13035
13036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13037
13038     if (oldGameMode == AnalyzeFile) {
13039       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13040       AnalyzeFileEvent();
13041     } else
13042     if (oldGameMode == AnalyzeMode) {
13043       AnalyzeFileEvent();
13044     }
13045
13046     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13047         long int w, b; // [HGM] adjourn: restore saved clock times
13048         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13049         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13050             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13051             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13052         }
13053     }
13054
13055     if(creatingBook) return TRUE;
13056     if (!matchMode && pos > 0) {
13057         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13058     } else
13059     if (matchMode || appData.timeDelay == 0) {
13060       ToEndEvent();
13061     } else if (appData.timeDelay > 0) {
13062       AutoPlayGameLoop();
13063     }
13064
13065     if (appData.debugMode)
13066         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13067
13068     loadFlag = 0; /* [HGM] true game starts */
13069     return TRUE;
13070 }
13071
13072 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13073 int
13074 ReloadPosition (int offset)
13075 {
13076     int positionNumber = lastLoadPositionNumber + offset;
13077     if (lastLoadPositionFP == NULL) {
13078         DisplayError(_("No position has been loaded yet"), 0);
13079         return FALSE;
13080     }
13081     if (positionNumber <= 0) {
13082         DisplayError(_("Can't back up any further"), 0);
13083         return FALSE;
13084     }
13085     return LoadPosition(lastLoadPositionFP, positionNumber,
13086                         lastLoadPositionTitle);
13087 }
13088
13089 /* Load the nth position from the given file */
13090 int
13091 LoadPositionFromFile (char *filename, int n, char *title)
13092 {
13093     FILE *f;
13094     char buf[MSG_SIZ];
13095
13096     if (strcmp(filename, "-") == 0) {
13097         return LoadPosition(stdin, n, "stdin");
13098     } else {
13099         f = fopen(filename, "rb");
13100         if (f == NULL) {
13101             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13102             DisplayError(buf, errno);
13103             return FALSE;
13104         } else {
13105             return LoadPosition(f, n, title);
13106         }
13107     }
13108 }
13109
13110 /* Load the nth position from the given open file, and close it */
13111 int
13112 LoadPosition (FILE *f, int positionNumber, char *title)
13113 {
13114     char *p, line[MSG_SIZ];
13115     Board initial_position;
13116     int i, j, fenMode, pn;
13117
13118     if (gameMode == Training )
13119         SetTrainingModeOff();
13120
13121     if (gameMode != BeginningOfGame) {
13122         Reset(FALSE, TRUE);
13123     }
13124     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13125         fclose(lastLoadPositionFP);
13126     }
13127     if (positionNumber == 0) positionNumber = 1;
13128     lastLoadPositionFP = f;
13129     lastLoadPositionNumber = positionNumber;
13130     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13131     if (first.pr == NoProc && !appData.noChessProgram) {
13132       StartChessProgram(&first);
13133       InitChessProgram(&first, FALSE);
13134     }
13135     pn = positionNumber;
13136     if (positionNumber < 0) {
13137         /* Negative position number means to seek to that byte offset */
13138         if (fseek(f, -positionNumber, 0) == -1) {
13139             DisplayError(_("Can't seek on position file"), 0);
13140             return FALSE;
13141         };
13142         pn = 1;
13143     } else {
13144         if (fseek(f, 0, 0) == -1) {
13145             if (f == lastLoadPositionFP ?
13146                 positionNumber == lastLoadPositionNumber + 1 :
13147                 positionNumber == 1) {
13148                 pn = 1;
13149             } else {
13150                 DisplayError(_("Can't seek on position file"), 0);
13151                 return FALSE;
13152             }
13153         }
13154     }
13155     /* See if this file is FEN or old-style xboard */
13156     if (fgets(line, MSG_SIZ, f) == NULL) {
13157         DisplayError(_("Position not found in file"), 0);
13158         return FALSE;
13159     }
13160     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13161     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13162
13163     if (pn >= 2) {
13164         if (fenMode || line[0] == '#') pn--;
13165         while (pn > 0) {
13166             /* skip positions before number pn */
13167             if (fgets(line, MSG_SIZ, f) == NULL) {
13168                 Reset(TRUE, TRUE);
13169                 DisplayError(_("Position not found in file"), 0);
13170                 return FALSE;
13171             }
13172             if (fenMode || line[0] == '#') pn--;
13173         }
13174     }
13175
13176     if (fenMode) {
13177         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13178             DisplayError(_("Bad FEN position in file"), 0);
13179             return FALSE;
13180         }
13181     } else {
13182         (void) fgets(line, MSG_SIZ, f);
13183         (void) fgets(line, MSG_SIZ, f);
13184
13185         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13186             (void) fgets(line, MSG_SIZ, f);
13187             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13188                 if (*p == ' ')
13189                   continue;
13190                 initial_position[i][j++] = CharToPiece(*p);
13191             }
13192         }
13193
13194         blackPlaysFirst = FALSE;
13195         if (!feof(f)) {
13196             (void) fgets(line, MSG_SIZ, f);
13197             if (strncmp(line, "black", strlen("black"))==0)
13198               blackPlaysFirst = TRUE;
13199         }
13200     }
13201     startedFromSetupPosition = TRUE;
13202
13203     CopyBoard(boards[0], initial_position);
13204     if (blackPlaysFirst) {
13205         currentMove = forwardMostMove = backwardMostMove = 1;
13206         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13207         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13208         CopyBoard(boards[1], initial_position);
13209         DisplayMessage("", _("Black to play"));
13210     } else {
13211         currentMove = forwardMostMove = backwardMostMove = 0;
13212         DisplayMessage("", _("White to play"));
13213     }
13214     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13215     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13216         SendToProgram("force\n", &first);
13217         SendBoard(&first, forwardMostMove);
13218     }
13219     if (appData.debugMode) {
13220 int i, j;
13221   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13222   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13223         fprintf(debugFP, "Load Position\n");
13224     }
13225
13226     if (positionNumber > 1) {
13227       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13228         DisplayTitle(line);
13229     } else {
13230         DisplayTitle(title);
13231     }
13232     gameMode = EditGame;
13233     ModeHighlight();
13234     ResetClocks();
13235     timeRemaining[0][1] = whiteTimeRemaining;
13236     timeRemaining[1][1] = blackTimeRemaining;
13237     DrawPosition(FALSE, boards[currentMove]);
13238
13239     return TRUE;
13240 }
13241
13242
13243 void
13244 CopyPlayerNameIntoFileName (char **dest, char *src)
13245 {
13246     while (*src != NULLCHAR && *src != ',') {
13247         if (*src == ' ') {
13248             *(*dest)++ = '_';
13249             src++;
13250         } else {
13251             *(*dest)++ = *src++;
13252         }
13253     }
13254 }
13255
13256 char *
13257 DefaultFileName (char *ext)
13258 {
13259     static char def[MSG_SIZ];
13260     char *p;
13261
13262     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13263         p = def;
13264         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13265         *p++ = '-';
13266         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13267         *p++ = '.';
13268         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13269     } else {
13270         def[0] = NULLCHAR;
13271     }
13272     return def;
13273 }
13274
13275 /* Save the current game to the given file */
13276 int
13277 SaveGameToFile (char *filename, int append)
13278 {
13279     FILE *f;
13280     char buf[MSG_SIZ];
13281     int result, i, t,tot=0;
13282
13283     if (strcmp(filename, "-") == 0) {
13284         return SaveGame(stdout, 0, NULL);
13285     } else {
13286         for(i=0; i<10; i++) { // upto 10 tries
13287              f = fopen(filename, append ? "a" : "w");
13288              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13289              if(f || errno != 13) break;
13290              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13291              tot += t;
13292         }
13293         if (f == NULL) {
13294             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13295             DisplayError(buf, errno);
13296             return FALSE;
13297         } else {
13298             safeStrCpy(buf, lastMsg, MSG_SIZ);
13299             DisplayMessage(_("Waiting for access to save file"), "");
13300             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13301             DisplayMessage(_("Saving game"), "");
13302             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13303             result = SaveGame(f, 0, NULL);
13304             DisplayMessage(buf, "");
13305             return result;
13306         }
13307     }
13308 }
13309
13310 char *
13311 SavePart (char *str)
13312 {
13313     static char buf[MSG_SIZ];
13314     char *p;
13315
13316     p = strchr(str, ' ');
13317     if (p == NULL) return str;
13318     strncpy(buf, str, p - str);
13319     buf[p - str] = NULLCHAR;
13320     return buf;
13321 }
13322
13323 #define PGN_MAX_LINE 75
13324
13325 #define PGN_SIDE_WHITE  0
13326 #define PGN_SIDE_BLACK  1
13327
13328 static int
13329 FindFirstMoveOutOfBook (int side)
13330 {
13331     int result = -1;
13332
13333     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13334         int index = backwardMostMove;
13335         int has_book_hit = 0;
13336
13337         if( (index % 2) != side ) {
13338             index++;
13339         }
13340
13341         while( index < forwardMostMove ) {
13342             /* Check to see if engine is in book */
13343             int depth = pvInfoList[index].depth;
13344             int score = pvInfoList[index].score;
13345             int in_book = 0;
13346
13347             if( depth <= 2 ) {
13348                 in_book = 1;
13349             }
13350             else if( score == 0 && depth == 63 ) {
13351                 in_book = 1; /* Zappa */
13352             }
13353             else if( score == 2 && depth == 99 ) {
13354                 in_book = 1; /* Abrok */
13355             }
13356
13357             has_book_hit += in_book;
13358
13359             if( ! in_book ) {
13360                 result = index;
13361
13362                 break;
13363             }
13364
13365             index += 2;
13366         }
13367     }
13368
13369     return result;
13370 }
13371
13372 void
13373 GetOutOfBookInfo (char * buf)
13374 {
13375     int oob[2];
13376     int i;
13377     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13378
13379     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13380     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13381
13382     *buf = '\0';
13383
13384     if( oob[0] >= 0 || oob[1] >= 0 ) {
13385         for( i=0; i<2; i++ ) {
13386             int idx = oob[i];
13387
13388             if( idx >= 0 ) {
13389                 if( i > 0 && oob[0] >= 0 ) {
13390                     strcat( buf, "   " );
13391                 }
13392
13393                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13394                 sprintf( buf+strlen(buf), "%s%.2f",
13395                     pvInfoList[idx].score >= 0 ? "+" : "",
13396                     pvInfoList[idx].score / 100.0 );
13397             }
13398         }
13399     }
13400 }
13401
13402 /* Save game in PGN style */
13403 static void
13404 SaveGamePGN2 (FILE *f)
13405 {
13406     int i, offset, linelen, newblock;
13407 //    char *movetext;
13408     char numtext[32];
13409     int movelen, numlen, blank;
13410     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13411
13412     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13413
13414     PrintPGNTags(f, &gameInfo);
13415
13416     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13417
13418     if (backwardMostMove > 0 || startedFromSetupPosition) {
13419         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13420         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13421         fprintf(f, "\n{--------------\n");
13422         PrintPosition(f, backwardMostMove);
13423         fprintf(f, "--------------}\n");
13424         free(fen);
13425     }
13426     else {
13427         /* [AS] Out of book annotation */
13428         if( appData.saveOutOfBookInfo ) {
13429             char buf[64];
13430
13431             GetOutOfBookInfo( buf );
13432
13433             if( buf[0] != '\0' ) {
13434                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13435             }
13436         }
13437
13438         fprintf(f, "\n");
13439     }
13440
13441     i = backwardMostMove;
13442     linelen = 0;
13443     newblock = TRUE;
13444
13445     while (i < forwardMostMove) {
13446         /* Print comments preceding this move */
13447         if (commentList[i] != NULL) {
13448             if (linelen > 0) fprintf(f, "\n");
13449             fprintf(f, "%s", commentList[i]);
13450             linelen = 0;
13451             newblock = TRUE;
13452         }
13453
13454         /* Format move number */
13455         if ((i % 2) == 0)
13456           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13457         else
13458           if (newblock)
13459             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13460           else
13461             numtext[0] = NULLCHAR;
13462
13463         numlen = strlen(numtext);
13464         newblock = FALSE;
13465
13466         /* Print move number */
13467         blank = linelen > 0 && numlen > 0;
13468         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13469             fprintf(f, "\n");
13470             linelen = 0;
13471             blank = 0;
13472         }
13473         if (blank) {
13474             fprintf(f, " ");
13475             linelen++;
13476         }
13477         fprintf(f, "%s", numtext);
13478         linelen += numlen;
13479
13480         /* Get move */
13481         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13482         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13483
13484         /* Print move */
13485         blank = linelen > 0 && movelen > 0;
13486         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13487             fprintf(f, "\n");
13488             linelen = 0;
13489             blank = 0;
13490         }
13491         if (blank) {
13492             fprintf(f, " ");
13493             linelen++;
13494         }
13495         fprintf(f, "%s", move_buffer);
13496         linelen += movelen;
13497
13498         /* [AS] Add PV info if present */
13499         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13500             /* [HGM] add time */
13501             char buf[MSG_SIZ]; int seconds;
13502
13503             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13504
13505             if( seconds <= 0)
13506               buf[0] = 0;
13507             else
13508               if( seconds < 30 )
13509                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13510               else
13511                 {
13512                   seconds = (seconds + 4)/10; // round to full seconds
13513                   if( seconds < 60 )
13514                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13515                   else
13516                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13517                 }
13518
13519             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13520                       pvInfoList[i].score >= 0 ? "+" : "",
13521                       pvInfoList[i].score / 100.0,
13522                       pvInfoList[i].depth,
13523                       buf );
13524
13525             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13526
13527             /* Print score/depth */
13528             blank = linelen > 0 && movelen > 0;
13529             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13530                 fprintf(f, "\n");
13531                 linelen = 0;
13532                 blank = 0;
13533             }
13534             if (blank) {
13535                 fprintf(f, " ");
13536                 linelen++;
13537             }
13538             fprintf(f, "%s", move_buffer);
13539             linelen += movelen;
13540         }
13541
13542         i++;
13543     }
13544
13545     /* Start a new line */
13546     if (linelen > 0) fprintf(f, "\n");
13547
13548     /* Print comments after last move */
13549     if (commentList[i] != NULL) {
13550         fprintf(f, "%s\n", commentList[i]);
13551     }
13552
13553     /* Print result */
13554     if (gameInfo.resultDetails != NULL &&
13555         gameInfo.resultDetails[0] != NULLCHAR) {
13556         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13557         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13558            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13559             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13560         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13561     } else {
13562         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13563     }
13564 }
13565
13566 /* Save game in PGN style and close the file */
13567 int
13568 SaveGamePGN (FILE *f)
13569 {
13570     SaveGamePGN2(f);
13571     fclose(f);
13572     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13573     return TRUE;
13574 }
13575
13576 /* Save game in old style and close the file */
13577 int
13578 SaveGameOldStyle (FILE *f)
13579 {
13580     int i, offset;
13581     time_t tm;
13582
13583     tm = time((time_t *) NULL);
13584
13585     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13586     PrintOpponents(f);
13587
13588     if (backwardMostMove > 0 || startedFromSetupPosition) {
13589         fprintf(f, "\n[--------------\n");
13590         PrintPosition(f, backwardMostMove);
13591         fprintf(f, "--------------]\n");
13592     } else {
13593         fprintf(f, "\n");
13594     }
13595
13596     i = backwardMostMove;
13597     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13598
13599     while (i < forwardMostMove) {
13600         if (commentList[i] != NULL) {
13601             fprintf(f, "[%s]\n", commentList[i]);
13602         }
13603
13604         if ((i % 2) == 1) {
13605             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13606             i++;
13607         } else {
13608             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13609             i++;
13610             if (commentList[i] != NULL) {
13611                 fprintf(f, "\n");
13612                 continue;
13613             }
13614             if (i >= forwardMostMove) {
13615                 fprintf(f, "\n");
13616                 break;
13617             }
13618             fprintf(f, "%s\n", parseList[i]);
13619             i++;
13620         }
13621     }
13622
13623     if (commentList[i] != NULL) {
13624         fprintf(f, "[%s]\n", commentList[i]);
13625     }
13626
13627     /* This isn't really the old style, but it's close enough */
13628     if (gameInfo.resultDetails != NULL &&
13629         gameInfo.resultDetails[0] != NULLCHAR) {
13630         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13631                 gameInfo.resultDetails);
13632     } else {
13633         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13634     }
13635
13636     fclose(f);
13637     return TRUE;
13638 }
13639
13640 /* Save the current game to open file f and close the file */
13641 int
13642 SaveGame (FILE *f, int dummy, char *dummy2)
13643 {
13644     if (gameMode == EditPosition) EditPositionDone(TRUE);
13645     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13646     if (appData.oldSaveStyle)
13647       return SaveGameOldStyle(f);
13648     else
13649       return SaveGamePGN(f);
13650 }
13651
13652 /* Save the current position to the given file */
13653 int
13654 SavePositionToFile (char *filename)
13655 {
13656     FILE *f;
13657     char buf[MSG_SIZ];
13658
13659     if (strcmp(filename, "-") == 0) {
13660         return SavePosition(stdout, 0, NULL);
13661     } else {
13662         f = fopen(filename, "a");
13663         if (f == NULL) {
13664             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13665             DisplayError(buf, errno);
13666             return FALSE;
13667         } else {
13668             safeStrCpy(buf, lastMsg, MSG_SIZ);
13669             DisplayMessage(_("Waiting for access to save file"), "");
13670             flock(fileno(f), LOCK_EX); // [HGM] lock
13671             DisplayMessage(_("Saving position"), "");
13672             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13673             SavePosition(f, 0, NULL);
13674             DisplayMessage(buf, "");
13675             return TRUE;
13676         }
13677     }
13678 }
13679
13680 /* Save the current position to the given open file and close the file */
13681 int
13682 SavePosition (FILE *f, int dummy, char *dummy2)
13683 {
13684     time_t tm;
13685     char *fen;
13686
13687     if (gameMode == EditPosition) EditPositionDone(TRUE);
13688     if (appData.oldSaveStyle) {
13689         tm = time((time_t *) NULL);
13690
13691         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13692         PrintOpponents(f);
13693         fprintf(f, "[--------------\n");
13694         PrintPosition(f, currentMove);
13695         fprintf(f, "--------------]\n");
13696     } else {
13697         fen = PositionToFEN(currentMove, NULL, 1);
13698         fprintf(f, "%s\n", fen);
13699         free(fen);
13700     }
13701     fclose(f);
13702     return TRUE;
13703 }
13704
13705 void
13706 ReloadCmailMsgEvent (int unregister)
13707 {
13708 #if !WIN32
13709     static char *inFilename = NULL;
13710     static char *outFilename;
13711     int i;
13712     struct stat inbuf, outbuf;
13713     int status;
13714
13715     /* Any registered moves are unregistered if unregister is set, */
13716     /* i.e. invoked by the signal handler */
13717     if (unregister) {
13718         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13719             cmailMoveRegistered[i] = FALSE;
13720             if (cmailCommentList[i] != NULL) {
13721                 free(cmailCommentList[i]);
13722                 cmailCommentList[i] = NULL;
13723             }
13724         }
13725         nCmailMovesRegistered = 0;
13726     }
13727
13728     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13729         cmailResult[i] = CMAIL_NOT_RESULT;
13730     }
13731     nCmailResults = 0;
13732
13733     if (inFilename == NULL) {
13734         /* Because the filenames are static they only get malloced once  */
13735         /* and they never get freed                                      */
13736         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13737         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13738
13739         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13740         sprintf(outFilename, "%s.out", appData.cmailGameName);
13741     }
13742
13743     status = stat(outFilename, &outbuf);
13744     if (status < 0) {
13745         cmailMailedMove = FALSE;
13746     } else {
13747         status = stat(inFilename, &inbuf);
13748         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13749     }
13750
13751     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13752        counts the games, notes how each one terminated, etc.
13753
13754        It would be nice to remove this kludge and instead gather all
13755        the information while building the game list.  (And to keep it
13756        in the game list nodes instead of having a bunch of fixed-size
13757        parallel arrays.)  Note this will require getting each game's
13758        termination from the PGN tags, as the game list builder does
13759        not process the game moves.  --mann
13760        */
13761     cmailMsgLoaded = TRUE;
13762     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13763
13764     /* Load first game in the file or popup game menu */
13765     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13766
13767 #endif /* !WIN32 */
13768     return;
13769 }
13770
13771 int
13772 RegisterMove ()
13773 {
13774     FILE *f;
13775     char string[MSG_SIZ];
13776
13777     if (   cmailMailedMove
13778         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13779         return TRUE;            /* Allow free viewing  */
13780     }
13781
13782     /* Unregister move to ensure that we don't leave RegisterMove        */
13783     /* with the move registered when the conditions for registering no   */
13784     /* longer hold                                                       */
13785     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13786         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13787         nCmailMovesRegistered --;
13788
13789         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13790           {
13791               free(cmailCommentList[lastLoadGameNumber - 1]);
13792               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13793           }
13794     }
13795
13796     if (cmailOldMove == -1) {
13797         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13798         return FALSE;
13799     }
13800
13801     if (currentMove > cmailOldMove + 1) {
13802         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13803         return FALSE;
13804     }
13805
13806     if (currentMove < cmailOldMove) {
13807         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13808         return FALSE;
13809     }
13810
13811     if (forwardMostMove > currentMove) {
13812         /* Silently truncate extra moves */
13813         TruncateGame();
13814     }
13815
13816     if (   (currentMove == cmailOldMove + 1)
13817         || (   (currentMove == cmailOldMove)
13818             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13819                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13820         if (gameInfo.result != GameUnfinished) {
13821             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13822         }
13823
13824         if (commentList[currentMove] != NULL) {
13825             cmailCommentList[lastLoadGameNumber - 1]
13826               = StrSave(commentList[currentMove]);
13827         }
13828         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13829
13830         if (appData.debugMode)
13831           fprintf(debugFP, "Saving %s for game %d\n",
13832                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13833
13834         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13835
13836         f = fopen(string, "w");
13837         if (appData.oldSaveStyle) {
13838             SaveGameOldStyle(f); /* also closes the file */
13839
13840             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13841             f = fopen(string, "w");
13842             SavePosition(f, 0, NULL); /* also closes the file */
13843         } else {
13844             fprintf(f, "{--------------\n");
13845             PrintPosition(f, currentMove);
13846             fprintf(f, "--------------}\n\n");
13847
13848             SaveGame(f, 0, NULL); /* also closes the file*/
13849         }
13850
13851         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13852         nCmailMovesRegistered ++;
13853     } else if (nCmailGames == 1) {
13854         DisplayError(_("You have not made a move yet"), 0);
13855         return FALSE;
13856     }
13857
13858     return TRUE;
13859 }
13860
13861 void
13862 MailMoveEvent ()
13863 {
13864 #if !WIN32
13865     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13866     FILE *commandOutput;
13867     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13868     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13869     int nBuffers;
13870     int i;
13871     int archived;
13872     char *arcDir;
13873
13874     if (! cmailMsgLoaded) {
13875         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13876         return;
13877     }
13878
13879     if (nCmailGames == nCmailResults) {
13880         DisplayError(_("No unfinished games"), 0);
13881         return;
13882     }
13883
13884 #if CMAIL_PROHIBIT_REMAIL
13885     if (cmailMailedMove) {
13886       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);
13887         DisplayError(msg, 0);
13888         return;
13889     }
13890 #endif
13891
13892     if (! (cmailMailedMove || RegisterMove())) return;
13893
13894     if (   cmailMailedMove
13895         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13896       snprintf(string, MSG_SIZ, partCommandString,
13897                appData.debugMode ? " -v" : "", appData.cmailGameName);
13898         commandOutput = popen(string, "r");
13899
13900         if (commandOutput == NULL) {
13901             DisplayError(_("Failed to invoke cmail"), 0);
13902         } else {
13903             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13904                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13905             }
13906             if (nBuffers > 1) {
13907                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13908                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13909                 nBytes = MSG_SIZ - 1;
13910             } else {
13911                 (void) memcpy(msg, buffer, nBytes);
13912             }
13913             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13914
13915             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13916                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13917
13918                 archived = TRUE;
13919                 for (i = 0; i < nCmailGames; i ++) {
13920                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13921                         archived = FALSE;
13922                     }
13923                 }
13924                 if (   archived
13925                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13926                         != NULL)) {
13927                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13928                            arcDir,
13929                            appData.cmailGameName,
13930                            gameInfo.date);
13931                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13932                     cmailMsgLoaded = FALSE;
13933                 }
13934             }
13935
13936             DisplayInformation(msg);
13937             pclose(commandOutput);
13938         }
13939     } else {
13940         if ((*cmailMsg) != '\0') {
13941             DisplayInformation(cmailMsg);
13942         }
13943     }
13944
13945     return;
13946 #endif /* !WIN32 */
13947 }
13948
13949 char *
13950 CmailMsg ()
13951 {
13952 #if WIN32
13953     return NULL;
13954 #else
13955     int  prependComma = 0;
13956     char number[5];
13957     char string[MSG_SIZ];       /* Space for game-list */
13958     int  i;
13959
13960     if (!cmailMsgLoaded) return "";
13961
13962     if (cmailMailedMove) {
13963       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13964     } else {
13965         /* Create a list of games left */
13966       snprintf(string, MSG_SIZ, "[");
13967         for (i = 0; i < nCmailGames; i ++) {
13968             if (! (   cmailMoveRegistered[i]
13969                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13970                 if (prependComma) {
13971                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13972                 } else {
13973                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13974                     prependComma = 1;
13975                 }
13976
13977                 strcat(string, number);
13978             }
13979         }
13980         strcat(string, "]");
13981
13982         if (nCmailMovesRegistered + nCmailResults == 0) {
13983             switch (nCmailGames) {
13984               case 1:
13985                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13986                 break;
13987
13988               case 2:
13989                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13990                 break;
13991
13992               default:
13993                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13994                          nCmailGames);
13995                 break;
13996             }
13997         } else {
13998             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13999               case 1:
14000                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14001                          string);
14002                 break;
14003
14004               case 0:
14005                 if (nCmailResults == nCmailGames) {
14006                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14007                 } else {
14008                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14009                 }
14010                 break;
14011
14012               default:
14013                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14014                          string);
14015             }
14016         }
14017     }
14018     return cmailMsg;
14019 #endif /* WIN32 */
14020 }
14021
14022 void
14023 ResetGameEvent ()
14024 {
14025     if (gameMode == Training)
14026       SetTrainingModeOff();
14027
14028     Reset(TRUE, TRUE);
14029     cmailMsgLoaded = FALSE;
14030     if (appData.icsActive) {
14031       SendToICS(ics_prefix);
14032       SendToICS("refresh\n");
14033     }
14034 }
14035
14036 void
14037 ExitEvent (int status)
14038 {
14039     exiting++;
14040     if (exiting > 2) {
14041       /* Give up on clean exit */
14042       exit(status);
14043     }
14044     if (exiting > 1) {
14045       /* Keep trying for clean exit */
14046       return;
14047     }
14048
14049     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14050     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14051
14052     if (telnetISR != NULL) {
14053       RemoveInputSource(telnetISR);
14054     }
14055     if (icsPR != NoProc) {
14056       DestroyChildProcess(icsPR, TRUE);
14057     }
14058
14059     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14060     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14061
14062     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14063     /* make sure this other one finishes before killing it!                  */
14064     if(endingGame) { int count = 0;
14065         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14066         while(endingGame && count++ < 10) DoSleep(1);
14067         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14068     }
14069
14070     /* Kill off chess programs */
14071     if (first.pr != NoProc) {
14072         ExitAnalyzeMode();
14073
14074         DoSleep( appData.delayBeforeQuit );
14075         SendToProgram("quit\n", &first);
14076         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14077     }
14078     if (second.pr != NoProc) {
14079         DoSleep( appData.delayBeforeQuit );
14080         SendToProgram("quit\n", &second);
14081         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14082     }
14083     if (first.isr != NULL) {
14084         RemoveInputSource(first.isr);
14085     }
14086     if (second.isr != NULL) {
14087         RemoveInputSource(second.isr);
14088     }
14089
14090     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14091     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14092
14093     ShutDownFrontEnd();
14094     exit(status);
14095 }
14096
14097 void
14098 PauseEngine (ChessProgramState *cps)
14099 {
14100     SendToProgram("pause\n", cps);
14101     cps->pause = 2;
14102 }
14103
14104 void
14105 UnPauseEngine (ChessProgramState *cps)
14106 {
14107     SendToProgram("resume\n", cps);
14108     cps->pause = 1;
14109 }
14110
14111 void
14112 PauseEvent ()
14113 {
14114     if (appData.debugMode)
14115         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14116     if (pausing) {
14117         pausing = FALSE;
14118         ModeHighlight();
14119         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14120             StartClocks();
14121             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14122                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14123                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14124             }
14125             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14126             HandleMachineMove(stashedInputMove, stalledEngine);
14127             stalledEngine = NULL;
14128             return;
14129         }
14130         if (gameMode == MachinePlaysWhite ||
14131             gameMode == TwoMachinesPlay   ||
14132             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14133             if(first.pause)  UnPauseEngine(&first);
14134             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14135             if(second.pause) UnPauseEngine(&second);
14136             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14137             StartClocks();
14138         } else {
14139             DisplayBothClocks();
14140         }
14141         if (gameMode == PlayFromGameFile) {
14142             if (appData.timeDelay >= 0)
14143                 AutoPlayGameLoop();
14144         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14145             Reset(FALSE, TRUE);
14146             SendToICS(ics_prefix);
14147             SendToICS("refresh\n");
14148         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14149             ForwardInner(forwardMostMove);
14150         }
14151         pauseExamInvalid = FALSE;
14152     } else {
14153         switch (gameMode) {
14154           default:
14155             return;
14156           case IcsExamining:
14157             pauseExamForwardMostMove = forwardMostMove;
14158             pauseExamInvalid = FALSE;
14159             /* fall through */
14160           case IcsObserving:
14161           case IcsPlayingWhite:
14162           case IcsPlayingBlack:
14163             pausing = TRUE;
14164             ModeHighlight();
14165             return;
14166           case PlayFromGameFile:
14167             (void) StopLoadGameTimer();
14168             pausing = TRUE;
14169             ModeHighlight();
14170             break;
14171           case BeginningOfGame:
14172             if (appData.icsActive) return;
14173             /* else fall through */
14174           case MachinePlaysWhite:
14175           case MachinePlaysBlack:
14176           case TwoMachinesPlay:
14177             if (forwardMostMove == 0)
14178               return;           /* don't pause if no one has moved */
14179             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14180                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14181                 if(onMove->pause) {           // thinking engine can be paused
14182                     PauseEngine(onMove);      // do it
14183                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14184                         PauseEngine(onMove->other);
14185                     else
14186                         SendToProgram("easy\n", onMove->other);
14187                     StopClocks();
14188                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14189             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14190                 if(first.pause) {
14191                     PauseEngine(&first);
14192                     StopClocks();
14193                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14194             } else { // human on move, pause pondering by either method
14195                 if(first.pause)
14196                     PauseEngine(&first);
14197                 else if(appData.ponderNextMove)
14198                     SendToProgram("easy\n", &first);
14199                 StopClocks();
14200             }
14201             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14202           case AnalyzeMode:
14203             pausing = TRUE;
14204             ModeHighlight();
14205             break;
14206         }
14207     }
14208 }
14209
14210 void
14211 EditCommentEvent ()
14212 {
14213     char title[MSG_SIZ];
14214
14215     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14216       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14217     } else {
14218       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14219                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14220                parseList[currentMove - 1]);
14221     }
14222
14223     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14224 }
14225
14226
14227 void
14228 EditTagsEvent ()
14229 {
14230     char *tags = PGNTags(&gameInfo);
14231     bookUp = FALSE;
14232     EditTagsPopUp(tags, NULL);
14233     free(tags);
14234 }
14235
14236 void
14237 ToggleSecond ()
14238 {
14239   if(second.analyzing) {
14240     SendToProgram("exit\n", &second);
14241     second.analyzing = FALSE;
14242   } else {
14243     if (second.pr == NoProc) StartChessProgram(&second);
14244     InitChessProgram(&second, FALSE);
14245     FeedMovesToProgram(&second, currentMove);
14246
14247     SendToProgram("analyze\n", &second);
14248     second.analyzing = TRUE;
14249   }
14250 }
14251
14252 /* Toggle ShowThinking */
14253 void
14254 ToggleShowThinking()
14255 {
14256   appData.showThinking = !appData.showThinking;
14257   ShowThinkingEvent();
14258 }
14259
14260 int
14261 AnalyzeModeEvent ()
14262 {
14263     char buf[MSG_SIZ];
14264
14265     if (!first.analysisSupport) {
14266       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14267       DisplayError(buf, 0);
14268       return 0;
14269     }
14270     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14271     if (appData.icsActive) {
14272         if (gameMode != IcsObserving) {
14273           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14274             DisplayError(buf, 0);
14275             /* secure check */
14276             if (appData.icsEngineAnalyze) {
14277                 if (appData.debugMode)
14278                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14279                 ExitAnalyzeMode();
14280                 ModeHighlight();
14281             }
14282             return 0;
14283         }
14284         /* if enable, user wants to disable icsEngineAnalyze */
14285         if (appData.icsEngineAnalyze) {
14286                 ExitAnalyzeMode();
14287                 ModeHighlight();
14288                 return 0;
14289         }
14290         appData.icsEngineAnalyze = TRUE;
14291         if (appData.debugMode)
14292             fprintf(debugFP, "ICS engine analyze starting... \n");
14293     }
14294
14295     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14296     if (appData.noChessProgram || gameMode == AnalyzeMode)
14297       return 0;
14298
14299     if (gameMode != AnalyzeFile) {
14300         if (!appData.icsEngineAnalyze) {
14301                EditGameEvent();
14302                if (gameMode != EditGame) return 0;
14303         }
14304         if (!appData.showThinking) ToggleShowThinking();
14305         ResurrectChessProgram();
14306         SendToProgram("analyze\n", &first);
14307         first.analyzing = TRUE;
14308         /*first.maybeThinking = TRUE;*/
14309         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14310         EngineOutputPopUp();
14311     }
14312     if (!appData.icsEngineAnalyze) {
14313         gameMode = AnalyzeMode;
14314         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14315     }
14316     pausing = FALSE;
14317     ModeHighlight();
14318     SetGameInfo();
14319
14320     StartAnalysisClock();
14321     GetTimeMark(&lastNodeCountTime);
14322     lastNodeCount = 0;
14323     return 1;
14324 }
14325
14326 void
14327 AnalyzeFileEvent ()
14328 {
14329     if (appData.noChessProgram || gameMode == AnalyzeFile)
14330       return;
14331
14332     if (!first.analysisSupport) {
14333       char buf[MSG_SIZ];
14334       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14335       DisplayError(buf, 0);
14336       return;
14337     }
14338
14339     if (gameMode != AnalyzeMode) {
14340         keepInfo = 1; // mere annotating should not alter PGN tags
14341         EditGameEvent();
14342         keepInfo = 0;
14343         if (gameMode != EditGame) return;
14344         if (!appData.showThinking) ToggleShowThinking();
14345         ResurrectChessProgram();
14346         SendToProgram("analyze\n", &first);
14347         first.analyzing = TRUE;
14348         /*first.maybeThinking = TRUE;*/
14349         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14350         EngineOutputPopUp();
14351     }
14352     gameMode = AnalyzeFile;
14353     pausing = FALSE;
14354     ModeHighlight();
14355
14356     StartAnalysisClock();
14357     GetTimeMark(&lastNodeCountTime);
14358     lastNodeCount = 0;
14359     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14360     AnalysisPeriodicEvent(1);
14361 }
14362
14363 void
14364 MachineWhiteEvent ()
14365 {
14366     char buf[MSG_SIZ];
14367     char *bookHit = NULL;
14368
14369     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14370       return;
14371
14372
14373     if (gameMode == PlayFromGameFile ||
14374         gameMode == TwoMachinesPlay  ||
14375         gameMode == Training         ||
14376         gameMode == AnalyzeMode      ||
14377         gameMode == EndOfGame)
14378         EditGameEvent();
14379
14380     if (gameMode == EditPosition)
14381         EditPositionDone(TRUE);
14382
14383     if (!WhiteOnMove(currentMove)) {
14384         DisplayError(_("It is not White's turn"), 0);
14385         return;
14386     }
14387
14388     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14389       ExitAnalyzeMode();
14390
14391     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14392         gameMode == AnalyzeFile)
14393         TruncateGame();
14394
14395     ResurrectChessProgram();    /* in case it isn't running */
14396     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14397         gameMode = MachinePlaysWhite;
14398         ResetClocks();
14399     } else
14400     gameMode = MachinePlaysWhite;
14401     pausing = FALSE;
14402     ModeHighlight();
14403     SetGameInfo();
14404     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14405     DisplayTitle(buf);
14406     if (first.sendName) {
14407       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14408       SendToProgram(buf, &first);
14409     }
14410     if (first.sendTime) {
14411       if (first.useColors) {
14412         SendToProgram("black\n", &first); /*gnu kludge*/
14413       }
14414       SendTimeRemaining(&first, TRUE);
14415     }
14416     if (first.useColors) {
14417       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14418     }
14419     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14420     SetMachineThinkingEnables();
14421     first.maybeThinking = TRUE;
14422     StartClocks();
14423     firstMove = FALSE;
14424
14425     if (appData.autoFlipView && !flipView) {
14426       flipView = !flipView;
14427       DrawPosition(FALSE, NULL);
14428       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14429     }
14430
14431     if(bookHit) { // [HGM] book: simulate book reply
14432         static char bookMove[MSG_SIZ]; // a bit generous?
14433
14434         programStats.nodes = programStats.depth = programStats.time =
14435         programStats.score = programStats.got_only_move = 0;
14436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14437
14438         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14439         strcat(bookMove, bookHit);
14440         HandleMachineMove(bookMove, &first);
14441     }
14442 }
14443
14444 void
14445 MachineBlackEvent ()
14446 {
14447   char buf[MSG_SIZ];
14448   char *bookHit = NULL;
14449
14450     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14451         return;
14452
14453
14454     if (gameMode == PlayFromGameFile ||
14455         gameMode == TwoMachinesPlay  ||
14456         gameMode == Training         ||
14457         gameMode == AnalyzeMode      ||
14458         gameMode == EndOfGame)
14459         EditGameEvent();
14460
14461     if (gameMode == EditPosition)
14462         EditPositionDone(TRUE);
14463
14464     if (WhiteOnMove(currentMove)) {
14465         DisplayError(_("It is not Black's turn"), 0);
14466         return;
14467     }
14468
14469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14470       ExitAnalyzeMode();
14471
14472     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14473         gameMode == AnalyzeFile)
14474         TruncateGame();
14475
14476     ResurrectChessProgram();    /* in case it isn't running */
14477     gameMode = MachinePlaysBlack;
14478     pausing = FALSE;
14479     ModeHighlight();
14480     SetGameInfo();
14481     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14482     DisplayTitle(buf);
14483     if (first.sendName) {
14484       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14485       SendToProgram(buf, &first);
14486     }
14487     if (first.sendTime) {
14488       if (first.useColors) {
14489         SendToProgram("white\n", &first); /*gnu kludge*/
14490       }
14491       SendTimeRemaining(&first, FALSE);
14492     }
14493     if (first.useColors) {
14494       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14495     }
14496     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14497     SetMachineThinkingEnables();
14498     first.maybeThinking = TRUE;
14499     StartClocks();
14500
14501     if (appData.autoFlipView && flipView) {
14502       flipView = !flipView;
14503       DrawPosition(FALSE, NULL);
14504       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14505     }
14506     if(bookHit) { // [HGM] book: simulate book reply
14507         static char bookMove[MSG_SIZ]; // a bit generous?
14508
14509         programStats.nodes = programStats.depth = programStats.time =
14510         programStats.score = programStats.got_only_move = 0;
14511         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14512
14513         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14514         strcat(bookMove, bookHit);
14515         HandleMachineMove(bookMove, &first);
14516     }
14517 }
14518
14519
14520 void
14521 DisplayTwoMachinesTitle ()
14522 {
14523     char buf[MSG_SIZ];
14524     if (appData.matchGames > 0) {
14525         if(appData.tourneyFile[0]) {
14526           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14527                    gameInfo.white, _("vs."), gameInfo.black,
14528                    nextGame+1, appData.matchGames+1,
14529                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14530         } else
14531         if (first.twoMachinesColor[0] == 'w') {
14532           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14533                    gameInfo.white, _("vs."),  gameInfo.black,
14534                    first.matchWins, second.matchWins,
14535                    matchGame - 1 - (first.matchWins + second.matchWins));
14536         } else {
14537           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14538                    gameInfo.white, _("vs."), gameInfo.black,
14539                    second.matchWins, first.matchWins,
14540                    matchGame - 1 - (first.matchWins + second.matchWins));
14541         }
14542     } else {
14543       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14544     }
14545     DisplayTitle(buf);
14546 }
14547
14548 void
14549 SettingsMenuIfReady ()
14550 {
14551   if (second.lastPing != second.lastPong) {
14552     DisplayMessage("", _("Waiting for second chess program"));
14553     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14554     return;
14555   }
14556   ThawUI();
14557   DisplayMessage("", "");
14558   SettingsPopUp(&second);
14559 }
14560
14561 int
14562 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14563 {
14564     char buf[MSG_SIZ];
14565     if (cps->pr == NoProc) {
14566         StartChessProgram(cps);
14567         if (cps->protocolVersion == 1) {
14568           retry();
14569           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14570         } else {
14571           /* kludge: allow timeout for initial "feature" command */
14572           if(retry != TwoMachinesEventIfReady) FreezeUI();
14573           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14574           DisplayMessage("", buf);
14575           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14576         }
14577         return 1;
14578     }
14579     return 0;
14580 }
14581
14582 void
14583 TwoMachinesEvent P((void))
14584 {
14585     int i;
14586     char buf[MSG_SIZ];
14587     ChessProgramState *onmove;
14588     char *bookHit = NULL;
14589     static int stalling = 0;
14590     TimeMark now;
14591     long wait;
14592
14593     if (appData.noChessProgram) return;
14594
14595     switch (gameMode) {
14596       case TwoMachinesPlay:
14597         return;
14598       case MachinePlaysWhite:
14599       case MachinePlaysBlack:
14600         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14601             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14602             return;
14603         }
14604         /* fall through */
14605       case BeginningOfGame:
14606       case PlayFromGameFile:
14607       case EndOfGame:
14608         EditGameEvent();
14609         if (gameMode != EditGame) return;
14610         break;
14611       case EditPosition:
14612         EditPositionDone(TRUE);
14613         break;
14614       case AnalyzeMode:
14615       case AnalyzeFile:
14616         ExitAnalyzeMode();
14617         break;
14618       case EditGame:
14619       default:
14620         break;
14621     }
14622
14623 //    forwardMostMove = currentMove;
14624     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14625     startingEngine = TRUE;
14626
14627     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14628
14629     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14630     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14631       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14632       return;
14633     }
14634     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14635
14636     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14637                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14638         startingEngine = FALSE;
14639         DisplayError("second engine does not play this", 0);
14640         return;
14641     }
14642
14643     if(!stalling) {
14644       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14645       SendToProgram("force\n", &second);
14646       stalling = 1;
14647       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14648       return;
14649     }
14650     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14651     if(appData.matchPause>10000 || appData.matchPause<10)
14652                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14653     wait = SubtractTimeMarks(&now, &pauseStart);
14654     if(wait < appData.matchPause) {
14655         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14656         return;
14657     }
14658     // we are now committed to starting the game
14659     stalling = 0;
14660     DisplayMessage("", "");
14661     if (startedFromSetupPosition) {
14662         SendBoard(&second, backwardMostMove);
14663     if (appData.debugMode) {
14664         fprintf(debugFP, "Two Machines\n");
14665     }
14666     }
14667     for (i = backwardMostMove; i < forwardMostMove; i++) {
14668         SendMoveToProgram(i, &second);
14669     }
14670
14671     gameMode = TwoMachinesPlay;
14672     pausing = startingEngine = FALSE;
14673     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14674     SetGameInfo();
14675     DisplayTwoMachinesTitle();
14676     firstMove = TRUE;
14677     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14678         onmove = &first;
14679     } else {
14680         onmove = &second;
14681     }
14682     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14683     SendToProgram(first.computerString, &first);
14684     if (first.sendName) {
14685       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14686       SendToProgram(buf, &first);
14687     }
14688     SendToProgram(second.computerString, &second);
14689     if (second.sendName) {
14690       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14691       SendToProgram(buf, &second);
14692     }
14693
14694     ResetClocks();
14695     if (!first.sendTime || !second.sendTime) {
14696         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14697         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14698     }
14699     if (onmove->sendTime) {
14700       if (onmove->useColors) {
14701         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14702       }
14703       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14704     }
14705     if (onmove->useColors) {
14706       SendToProgram(onmove->twoMachinesColor, onmove);
14707     }
14708     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14709 //    SendToProgram("go\n", onmove);
14710     onmove->maybeThinking = TRUE;
14711     SetMachineThinkingEnables();
14712
14713     StartClocks();
14714
14715     if(bookHit) { // [HGM] book: simulate book reply
14716         static char bookMove[MSG_SIZ]; // a bit generous?
14717
14718         programStats.nodes = programStats.depth = programStats.time =
14719         programStats.score = programStats.got_only_move = 0;
14720         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14721
14722         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14723         strcat(bookMove, bookHit);
14724         savedMessage = bookMove; // args for deferred call
14725         savedState = onmove;
14726         ScheduleDelayedEvent(DeferredBookMove, 1);
14727     }
14728 }
14729
14730 void
14731 TrainingEvent ()
14732 {
14733     if (gameMode == Training) {
14734       SetTrainingModeOff();
14735       gameMode = PlayFromGameFile;
14736       DisplayMessage("", _("Training mode off"));
14737     } else {
14738       gameMode = Training;
14739       animateTraining = appData.animate;
14740
14741       /* make sure we are not already at the end of the game */
14742       if (currentMove < forwardMostMove) {
14743         SetTrainingModeOn();
14744         DisplayMessage("", _("Training mode on"));
14745       } else {
14746         gameMode = PlayFromGameFile;
14747         DisplayError(_("Already at end of game"), 0);
14748       }
14749     }
14750     ModeHighlight();
14751 }
14752
14753 void
14754 IcsClientEvent ()
14755 {
14756     if (!appData.icsActive) return;
14757     switch (gameMode) {
14758       case IcsPlayingWhite:
14759       case IcsPlayingBlack:
14760       case IcsObserving:
14761       case IcsIdle:
14762       case BeginningOfGame:
14763       case IcsExamining:
14764         return;
14765
14766       case EditGame:
14767         break;
14768
14769       case EditPosition:
14770         EditPositionDone(TRUE);
14771         break;
14772
14773       case AnalyzeMode:
14774       case AnalyzeFile:
14775         ExitAnalyzeMode();
14776         break;
14777
14778       default:
14779         EditGameEvent();
14780         break;
14781     }
14782
14783     gameMode = IcsIdle;
14784     ModeHighlight();
14785     return;
14786 }
14787
14788 void
14789 EditGameEvent ()
14790 {
14791     int i;
14792
14793     switch (gameMode) {
14794       case Training:
14795         SetTrainingModeOff();
14796         break;
14797       case MachinePlaysWhite:
14798       case MachinePlaysBlack:
14799       case BeginningOfGame:
14800         SendToProgram("force\n", &first);
14801         SetUserThinkingEnables();
14802         break;
14803       case PlayFromGameFile:
14804         (void) StopLoadGameTimer();
14805         if (gameFileFP != NULL) {
14806             gameFileFP = NULL;
14807         }
14808         break;
14809       case EditPosition:
14810         EditPositionDone(TRUE);
14811         break;
14812       case AnalyzeMode:
14813       case AnalyzeFile:
14814         ExitAnalyzeMode();
14815         SendToProgram("force\n", &first);
14816         break;
14817       case TwoMachinesPlay:
14818         GameEnds(EndOfFile, NULL, GE_PLAYER);
14819         ResurrectChessProgram();
14820         SetUserThinkingEnables();
14821         break;
14822       case EndOfGame:
14823         ResurrectChessProgram();
14824         break;
14825       case IcsPlayingBlack:
14826       case IcsPlayingWhite:
14827         DisplayError(_("Warning: You are still playing a game"), 0);
14828         break;
14829       case IcsObserving:
14830         DisplayError(_("Warning: You are still observing a game"), 0);
14831         break;
14832       case IcsExamining:
14833         DisplayError(_("Warning: You are still examining a game"), 0);
14834         break;
14835       case IcsIdle:
14836         break;
14837       case EditGame:
14838       default:
14839         return;
14840     }
14841
14842     pausing = FALSE;
14843     StopClocks();
14844     first.offeredDraw = second.offeredDraw = 0;
14845
14846     if (gameMode == PlayFromGameFile) {
14847         whiteTimeRemaining = timeRemaining[0][currentMove];
14848         blackTimeRemaining = timeRemaining[1][currentMove];
14849         DisplayTitle("");
14850     }
14851
14852     if (gameMode == MachinePlaysWhite ||
14853         gameMode == MachinePlaysBlack ||
14854         gameMode == TwoMachinesPlay ||
14855         gameMode == EndOfGame) {
14856         i = forwardMostMove;
14857         while (i > currentMove) {
14858             SendToProgram("undo\n", &first);
14859             i--;
14860         }
14861         if(!adjustedClock) {
14862         whiteTimeRemaining = timeRemaining[0][currentMove];
14863         blackTimeRemaining = timeRemaining[1][currentMove];
14864         DisplayBothClocks();
14865         }
14866         if (whiteFlag || blackFlag) {
14867             whiteFlag = blackFlag = 0;
14868         }
14869         DisplayTitle("");
14870     }
14871
14872     gameMode = EditGame;
14873     ModeHighlight();
14874     SetGameInfo();
14875 }
14876
14877
14878 void
14879 EditPositionEvent ()
14880 {
14881     if (gameMode == EditPosition) {
14882         EditGameEvent();
14883         return;
14884     }
14885
14886     EditGameEvent();
14887     if (gameMode != EditGame) return;
14888
14889     gameMode = EditPosition;
14890     ModeHighlight();
14891     SetGameInfo();
14892     if (currentMove > 0)
14893       CopyBoard(boards[0], boards[currentMove]);
14894
14895     blackPlaysFirst = !WhiteOnMove(currentMove);
14896     ResetClocks();
14897     currentMove = forwardMostMove = backwardMostMove = 0;
14898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14899     DisplayMove(-1);
14900     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14901 }
14902
14903 void
14904 ExitAnalyzeMode ()
14905 {
14906     /* [DM] icsEngineAnalyze - possible call from other functions */
14907     if (appData.icsEngineAnalyze) {
14908         appData.icsEngineAnalyze = FALSE;
14909
14910         DisplayMessage("",_("Close ICS engine analyze..."));
14911     }
14912     if (first.analysisSupport && first.analyzing) {
14913       SendToBoth("exit\n");
14914       first.analyzing = second.analyzing = FALSE;
14915     }
14916     thinkOutput[0] = NULLCHAR;
14917 }
14918
14919 void
14920 EditPositionDone (Boolean fakeRights)
14921 {
14922     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14923
14924     startedFromSetupPosition = TRUE;
14925     InitChessProgram(&first, FALSE);
14926     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14927       boards[0][EP_STATUS] = EP_NONE;
14928       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14929       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14930         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14931         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14932       } else boards[0][CASTLING][2] = NoRights;
14933       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14934         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14935         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14936       } else boards[0][CASTLING][5] = NoRights;
14937       if(gameInfo.variant == VariantSChess) {
14938         int i;
14939         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14940           boards[0][VIRGIN][i] = 0;
14941           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14942           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14943         }
14944       }
14945     }
14946     SendToProgram("force\n", &first);
14947     if (blackPlaysFirst) {
14948         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14949         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14950         currentMove = forwardMostMove = backwardMostMove = 1;
14951         CopyBoard(boards[1], boards[0]);
14952     } else {
14953         currentMove = forwardMostMove = backwardMostMove = 0;
14954     }
14955     SendBoard(&first, forwardMostMove);
14956     if (appData.debugMode) {
14957         fprintf(debugFP, "EditPosDone\n");
14958     }
14959     DisplayTitle("");
14960     DisplayMessage("", "");
14961     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14962     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14963     gameMode = EditGame;
14964     ModeHighlight();
14965     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14966     ClearHighlights(); /* [AS] */
14967 }
14968
14969 /* Pause for `ms' milliseconds */
14970 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14971 void
14972 TimeDelay (long ms)
14973 {
14974     TimeMark m1, m2;
14975
14976     GetTimeMark(&m1);
14977     do {
14978         GetTimeMark(&m2);
14979     } while (SubtractTimeMarks(&m2, &m1) < ms);
14980 }
14981
14982 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14983 void
14984 SendMultiLineToICS (char *buf)
14985 {
14986     char temp[MSG_SIZ+1], *p;
14987     int len;
14988
14989     len = strlen(buf);
14990     if (len > MSG_SIZ)
14991       len = MSG_SIZ;
14992
14993     strncpy(temp, buf, len);
14994     temp[len] = 0;
14995
14996     p = temp;
14997     while (*p) {
14998         if (*p == '\n' || *p == '\r')
14999           *p = ' ';
15000         ++p;
15001     }
15002
15003     strcat(temp, "\n");
15004     SendToICS(temp);
15005     SendToPlayer(temp, strlen(temp));
15006 }
15007
15008 void
15009 SetWhiteToPlayEvent ()
15010 {
15011     if (gameMode == EditPosition) {
15012         blackPlaysFirst = FALSE;
15013         DisplayBothClocks();    /* works because currentMove is 0 */
15014     } else if (gameMode == IcsExamining) {
15015         SendToICS(ics_prefix);
15016         SendToICS("tomove white\n");
15017     }
15018 }
15019
15020 void
15021 SetBlackToPlayEvent ()
15022 {
15023     if (gameMode == EditPosition) {
15024         blackPlaysFirst = TRUE;
15025         currentMove = 1;        /* kludge */
15026         DisplayBothClocks();
15027         currentMove = 0;
15028     } else if (gameMode == IcsExamining) {
15029         SendToICS(ics_prefix);
15030         SendToICS("tomove black\n");
15031     }
15032 }
15033
15034 void
15035 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15036 {
15037     char buf[MSG_SIZ];
15038     ChessSquare piece = boards[0][y][x];
15039     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15040     static int lastVariant;
15041
15042     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15043
15044     switch (selection) {
15045       case ClearBoard:
15046         CopyBoard(currentBoard, boards[0]);
15047         CopyBoard(menuBoard, initialPosition);
15048         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15049             SendToICS(ics_prefix);
15050             SendToICS("bsetup clear\n");
15051         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15052             SendToICS(ics_prefix);
15053             SendToICS("clearboard\n");
15054         } else {
15055             int nonEmpty = 0;
15056             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15057                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15058                 for (y = 0; y < BOARD_HEIGHT; y++) {
15059                     if (gameMode == IcsExamining) {
15060                         if (boards[currentMove][y][x] != EmptySquare) {
15061                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15062                                     AAA + x, ONE + y);
15063                             SendToICS(buf);
15064                         }
15065                     } else {
15066                         if(boards[0][y][x] != p) nonEmpty++;
15067                         boards[0][y][x] = p;
15068                     }
15069                 }
15070             }
15071             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15072                 int r;
15073                 for(r = 0; r < BOARD_HEIGHT; r++) {
15074                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15075                     ChessSquare p = menuBoard[r][x];
15076                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15077                   }
15078                 }
15079                 DisplayMessage("Clicking clock again restores position", "");
15080                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15081                 if(!nonEmpty) { // asked to clear an empty board
15082                     CopyBoard(boards[0], menuBoard);
15083                 } else
15084                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15085                     CopyBoard(boards[0], initialPosition);
15086                 } else
15087                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15088                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15089                     CopyBoard(boards[0], erasedBoard);
15090                 } else
15091                     CopyBoard(erasedBoard, currentBoard);
15092
15093             }
15094         }
15095         if (gameMode == EditPosition) {
15096             DrawPosition(FALSE, boards[0]);
15097         }
15098         break;
15099
15100       case WhitePlay:
15101         SetWhiteToPlayEvent();
15102         break;
15103
15104       case BlackPlay:
15105         SetBlackToPlayEvent();
15106         break;
15107
15108       case EmptySquare:
15109         if (gameMode == IcsExamining) {
15110             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15111             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15112             SendToICS(buf);
15113         } else {
15114             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15115                 if(x == BOARD_LEFT-2) {
15116                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15117                     boards[0][y][1] = 0;
15118                 } else
15119                 if(x == BOARD_RGHT+1) {
15120                     if(y >= gameInfo.holdingsSize) break;
15121                     boards[0][y][BOARD_WIDTH-2] = 0;
15122                 } else break;
15123             }
15124             boards[0][y][x] = EmptySquare;
15125             DrawPosition(FALSE, boards[0]);
15126         }
15127         break;
15128
15129       case PromotePiece:
15130         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15131            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15132             selection = (ChessSquare) (PROMOTED piece);
15133         } else if(piece == EmptySquare) selection = WhiteSilver;
15134         else selection = (ChessSquare)((int)piece - 1);
15135         goto defaultlabel;
15136
15137       case DemotePiece:
15138         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15139            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15140             selection = (ChessSquare) (DEMOTED piece);
15141         } else if(piece == EmptySquare) selection = BlackSilver;
15142         else selection = (ChessSquare)((int)piece + 1);
15143         goto defaultlabel;
15144
15145       case WhiteQueen:
15146       case BlackQueen:
15147         if(gameInfo.variant == VariantShatranj ||
15148            gameInfo.variant == VariantXiangqi  ||
15149            gameInfo.variant == VariantCourier  ||
15150            gameInfo.variant == VariantASEAN    ||
15151            gameInfo.variant == VariantMakruk     )
15152             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15153         goto defaultlabel;
15154
15155       case WhiteKing:
15156       case BlackKing:
15157         if(gameInfo.variant == VariantXiangqi)
15158             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15159         if(gameInfo.variant == VariantKnightmate)
15160             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15161       default:
15162         defaultlabel:
15163         if (gameMode == IcsExamining) {
15164             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15165             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15166                      PieceToChar(selection), AAA + x, ONE + y);
15167             SendToICS(buf);
15168         } else {
15169             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15170                 int n;
15171                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15172                     n = PieceToNumber(selection - BlackPawn);
15173                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15174                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15175                     boards[0][BOARD_HEIGHT-1-n][1]++;
15176                 } else
15177                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15178                     n = PieceToNumber(selection);
15179                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15180                     boards[0][n][BOARD_WIDTH-1] = selection;
15181                     boards[0][n][BOARD_WIDTH-2]++;
15182                 }
15183             } else
15184             boards[0][y][x] = selection;
15185             DrawPosition(TRUE, boards[0]);
15186             ClearHighlights();
15187             fromX = fromY = -1;
15188         }
15189         break;
15190     }
15191 }
15192
15193
15194 void
15195 DropMenuEvent (ChessSquare selection, int x, int y)
15196 {
15197     ChessMove moveType;
15198
15199     switch (gameMode) {
15200       case IcsPlayingWhite:
15201       case MachinePlaysBlack:
15202         if (!WhiteOnMove(currentMove)) {
15203             DisplayMoveError(_("It is Black's turn"));
15204             return;
15205         }
15206         moveType = WhiteDrop;
15207         break;
15208       case IcsPlayingBlack:
15209       case MachinePlaysWhite:
15210         if (WhiteOnMove(currentMove)) {
15211             DisplayMoveError(_("It is White's turn"));
15212             return;
15213         }
15214         moveType = BlackDrop;
15215         break;
15216       case EditGame:
15217         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15218         break;
15219       default:
15220         return;
15221     }
15222
15223     if (moveType == BlackDrop && selection < BlackPawn) {
15224       selection = (ChessSquare) ((int) selection
15225                                  + (int) BlackPawn - (int) WhitePawn);
15226     }
15227     if (boards[currentMove][y][x] != EmptySquare) {
15228         DisplayMoveError(_("That square is occupied"));
15229         return;
15230     }
15231
15232     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15233 }
15234
15235 void
15236 AcceptEvent ()
15237 {
15238     /* Accept a pending offer of any kind from opponent */
15239
15240     if (appData.icsActive) {
15241         SendToICS(ics_prefix);
15242         SendToICS("accept\n");
15243     } else if (cmailMsgLoaded) {
15244         if (currentMove == cmailOldMove &&
15245             commentList[cmailOldMove] != NULL &&
15246             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15247                    "Black offers a draw" : "White offers a draw")) {
15248             TruncateGame();
15249             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15250             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15251         } else {
15252             DisplayError(_("There is no pending offer on this move"), 0);
15253             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15254         }
15255     } else {
15256         /* Not used for offers from chess program */
15257     }
15258 }
15259
15260 void
15261 DeclineEvent ()
15262 {
15263     /* Decline a pending offer of any kind from opponent */
15264
15265     if (appData.icsActive) {
15266         SendToICS(ics_prefix);
15267         SendToICS("decline\n");
15268     } else if (cmailMsgLoaded) {
15269         if (currentMove == cmailOldMove &&
15270             commentList[cmailOldMove] != NULL &&
15271             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15272                    "Black offers a draw" : "White offers a draw")) {
15273 #ifdef NOTDEF
15274             AppendComment(cmailOldMove, "Draw declined", TRUE);
15275             DisplayComment(cmailOldMove - 1, "Draw declined");
15276 #endif /*NOTDEF*/
15277         } else {
15278             DisplayError(_("There is no pending offer on this move"), 0);
15279         }
15280     } else {
15281         /* Not used for offers from chess program */
15282     }
15283 }
15284
15285 void
15286 RematchEvent ()
15287 {
15288     /* Issue ICS rematch command */
15289     if (appData.icsActive) {
15290         SendToICS(ics_prefix);
15291         SendToICS("rematch\n");
15292     }
15293 }
15294
15295 void
15296 CallFlagEvent ()
15297 {
15298     /* Call your opponent's flag (claim a win on time) */
15299     if (appData.icsActive) {
15300         SendToICS(ics_prefix);
15301         SendToICS("flag\n");
15302     } else {
15303         switch (gameMode) {
15304           default:
15305             return;
15306           case MachinePlaysWhite:
15307             if (whiteFlag) {
15308                 if (blackFlag)
15309                   GameEnds(GameIsDrawn, "Both players ran out of time",
15310                            GE_PLAYER);
15311                 else
15312                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15313             } else {
15314                 DisplayError(_("Your opponent is not out of time"), 0);
15315             }
15316             break;
15317           case MachinePlaysBlack:
15318             if (blackFlag) {
15319                 if (whiteFlag)
15320                   GameEnds(GameIsDrawn, "Both players ran out of time",
15321                            GE_PLAYER);
15322                 else
15323                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15324             } else {
15325                 DisplayError(_("Your opponent is not out of time"), 0);
15326             }
15327             break;
15328         }
15329     }
15330 }
15331
15332 void
15333 ClockClick (int which)
15334 {       // [HGM] code moved to back-end from winboard.c
15335         if(which) { // black clock
15336           if (gameMode == EditPosition || gameMode == IcsExamining) {
15337             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15338             SetBlackToPlayEvent();
15339           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15340                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15341           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15342           } else if (shiftKey) {
15343             AdjustClock(which, -1);
15344           } else if (gameMode == IcsPlayingWhite ||
15345                      gameMode == MachinePlaysBlack) {
15346             CallFlagEvent();
15347           }
15348         } else { // white clock
15349           if (gameMode == EditPosition || gameMode == IcsExamining) {
15350             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15351             SetWhiteToPlayEvent();
15352           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15353                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15354           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15355           } else if (shiftKey) {
15356             AdjustClock(which, -1);
15357           } else if (gameMode == IcsPlayingBlack ||
15358                    gameMode == MachinePlaysWhite) {
15359             CallFlagEvent();
15360           }
15361         }
15362 }
15363
15364 void
15365 DrawEvent ()
15366 {
15367     /* Offer draw or accept pending draw offer from opponent */
15368
15369     if (appData.icsActive) {
15370         /* Note: tournament rules require draw offers to be
15371            made after you make your move but before you punch
15372            your clock.  Currently ICS doesn't let you do that;
15373            instead, you immediately punch your clock after making
15374            a move, but you can offer a draw at any time. */
15375
15376         SendToICS(ics_prefix);
15377         SendToICS("draw\n");
15378         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15379     } else if (cmailMsgLoaded) {
15380         if (currentMove == cmailOldMove &&
15381             commentList[cmailOldMove] != NULL &&
15382             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15383                    "Black offers a draw" : "White offers a draw")) {
15384             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15386         } else if (currentMove == cmailOldMove + 1) {
15387             char *offer = WhiteOnMove(cmailOldMove) ?
15388               "White offers a draw" : "Black offers a draw";
15389             AppendComment(currentMove, offer, TRUE);
15390             DisplayComment(currentMove - 1, offer);
15391             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15392         } else {
15393             DisplayError(_("You must make your move before offering a draw"), 0);
15394             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15395         }
15396     } else if (first.offeredDraw) {
15397         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15398     } else {
15399         if (first.sendDrawOffers) {
15400             SendToProgram("draw\n", &first);
15401             userOfferedDraw = TRUE;
15402         }
15403     }
15404 }
15405
15406 void
15407 AdjournEvent ()
15408 {
15409     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15410
15411     if (appData.icsActive) {
15412         SendToICS(ics_prefix);
15413         SendToICS("adjourn\n");
15414     } else {
15415         /* Currently GNU Chess doesn't offer or accept Adjourns */
15416     }
15417 }
15418
15419
15420 void
15421 AbortEvent ()
15422 {
15423     /* Offer Abort or accept pending Abort offer from opponent */
15424
15425     if (appData.icsActive) {
15426         SendToICS(ics_prefix);
15427         SendToICS("abort\n");
15428     } else {
15429         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15430     }
15431 }
15432
15433 void
15434 ResignEvent ()
15435 {
15436     /* Resign.  You can do this even if it's not your turn. */
15437
15438     if (appData.icsActive) {
15439         SendToICS(ics_prefix);
15440         SendToICS("resign\n");
15441     } else {
15442         switch (gameMode) {
15443           case MachinePlaysWhite:
15444             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15445             break;
15446           case MachinePlaysBlack:
15447             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15448             break;
15449           case EditGame:
15450             if (cmailMsgLoaded) {
15451                 TruncateGame();
15452                 if (WhiteOnMove(cmailOldMove)) {
15453                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15454                 } else {
15455                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15456                 }
15457                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15458             }
15459             break;
15460           default:
15461             break;
15462         }
15463     }
15464 }
15465
15466
15467 void
15468 StopObservingEvent ()
15469 {
15470     /* Stop observing current games */
15471     SendToICS(ics_prefix);
15472     SendToICS("unobserve\n");
15473 }
15474
15475 void
15476 StopExaminingEvent ()
15477 {
15478     /* Stop observing current game */
15479     SendToICS(ics_prefix);
15480     SendToICS("unexamine\n");
15481 }
15482
15483 void
15484 ForwardInner (int target)
15485 {
15486     int limit; int oldSeekGraphUp = seekGraphUp;
15487
15488     if (appData.debugMode)
15489         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15490                 target, currentMove, forwardMostMove);
15491
15492     if (gameMode == EditPosition)
15493       return;
15494
15495     seekGraphUp = FALSE;
15496     MarkTargetSquares(1);
15497
15498     if (gameMode == PlayFromGameFile && !pausing)
15499       PauseEvent();
15500
15501     if (gameMode == IcsExamining && pausing)
15502       limit = pauseExamForwardMostMove;
15503     else
15504       limit = forwardMostMove;
15505
15506     if (target > limit) target = limit;
15507
15508     if (target > 0 && moveList[target - 1][0]) {
15509         int fromX, fromY, toX, toY;
15510         toX = moveList[target - 1][2] - AAA;
15511         toY = moveList[target - 1][3] - ONE;
15512         if (moveList[target - 1][1] == '@') {
15513             if (appData.highlightLastMove) {
15514                 SetHighlights(-1, -1, toX, toY);
15515             }
15516         } else {
15517             int viaX = moveList[target - 1][5] - AAA;
15518             int viaY = moveList[target - 1][6] - ONE;
15519             fromX = moveList[target - 1][0] - AAA;
15520             fromY = moveList[target - 1][1] - ONE;
15521             if (target == currentMove + 1) {
15522                 if(moveList[target - 1][4] == ';') { // multi-leg
15523                     ChessSquare piece = boards[currentMove][viaY][viaX];
15524                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15525                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15526                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15527                     boards[currentMove][viaY][viaX] = piece;
15528                 } else
15529                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15530             }
15531             if (appData.highlightLastMove) {
15532                 SetHighlights(fromX, fromY, toX, toY);
15533             }
15534         }
15535     }
15536     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15537         gameMode == Training || gameMode == PlayFromGameFile ||
15538         gameMode == AnalyzeFile) {
15539         while (currentMove < target) {
15540             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15541             SendMoveToProgram(currentMove++, &first);
15542         }
15543     } else {
15544         currentMove = target;
15545     }
15546
15547     if (gameMode == EditGame || gameMode == EndOfGame) {
15548         whiteTimeRemaining = timeRemaining[0][currentMove];
15549         blackTimeRemaining = timeRemaining[1][currentMove];
15550     }
15551     DisplayBothClocks();
15552     DisplayMove(currentMove - 1);
15553     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15554     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15555     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15556         DisplayComment(currentMove - 1, commentList[currentMove]);
15557     }
15558     ClearMap(); // [HGM] exclude: invalidate map
15559 }
15560
15561
15562 void
15563 ForwardEvent ()
15564 {
15565     if (gameMode == IcsExamining && !pausing) {
15566         SendToICS(ics_prefix);
15567         SendToICS("forward\n");
15568     } else {
15569         ForwardInner(currentMove + 1);
15570     }
15571 }
15572
15573 void
15574 ToEndEvent ()
15575 {
15576     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15577         /* to optimze, we temporarily turn off analysis mode while we feed
15578          * the remaining moves to the engine. Otherwise we get analysis output
15579          * after each move.
15580          */
15581         if (first.analysisSupport) {
15582           SendToProgram("exit\nforce\n", &first);
15583           first.analyzing = FALSE;
15584         }
15585     }
15586
15587     if (gameMode == IcsExamining && !pausing) {
15588         SendToICS(ics_prefix);
15589         SendToICS("forward 999999\n");
15590     } else {
15591         ForwardInner(forwardMostMove);
15592     }
15593
15594     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15595         /* we have fed all the moves, so reactivate analysis mode */
15596         SendToProgram("analyze\n", &first);
15597         first.analyzing = TRUE;
15598         /*first.maybeThinking = TRUE;*/
15599         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15600     }
15601 }
15602
15603 void
15604 BackwardInner (int target)
15605 {
15606     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15607
15608     if (appData.debugMode)
15609         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15610                 target, currentMove, forwardMostMove);
15611
15612     if (gameMode == EditPosition) return;
15613     seekGraphUp = FALSE;
15614     MarkTargetSquares(1);
15615     if (currentMove <= backwardMostMove) {
15616         ClearHighlights();
15617         DrawPosition(full_redraw, boards[currentMove]);
15618         return;
15619     }
15620     if (gameMode == PlayFromGameFile && !pausing)
15621       PauseEvent();
15622
15623     if (moveList[target][0]) {
15624         int fromX, fromY, toX, toY;
15625         toX = moveList[target][2] - AAA;
15626         toY = moveList[target][3] - ONE;
15627         if (moveList[target][1] == '@') {
15628             if (appData.highlightLastMove) {
15629                 SetHighlights(-1, -1, toX, toY);
15630             }
15631         } else {
15632             fromX = moveList[target][0] - AAA;
15633             fromY = moveList[target][1] - ONE;
15634             if (target == currentMove - 1) {
15635                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15636             }
15637             if (appData.highlightLastMove) {
15638                 SetHighlights(fromX, fromY, toX, toY);
15639             }
15640         }
15641     }
15642     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15643         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15644         while (currentMove > target) {
15645             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15646                 // null move cannot be undone. Reload program with move history before it.
15647                 int i;
15648                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15649                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15650                 }
15651                 SendBoard(&first, i);
15652               if(second.analyzing) SendBoard(&second, i);
15653                 for(currentMove=i; currentMove<target; currentMove++) {
15654                     SendMoveToProgram(currentMove, &first);
15655                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15656                 }
15657                 break;
15658             }
15659             SendToBoth("undo\n");
15660             currentMove--;
15661         }
15662     } else {
15663         currentMove = target;
15664     }
15665
15666     if (gameMode == EditGame || gameMode == EndOfGame) {
15667         whiteTimeRemaining = timeRemaining[0][currentMove];
15668         blackTimeRemaining = timeRemaining[1][currentMove];
15669     }
15670     DisplayBothClocks();
15671     DisplayMove(currentMove - 1);
15672     DrawPosition(full_redraw, boards[currentMove]);
15673     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15674     // [HGM] PV info: routine tests if comment empty
15675     DisplayComment(currentMove - 1, commentList[currentMove]);
15676     ClearMap(); // [HGM] exclude: invalidate map
15677 }
15678
15679 void
15680 BackwardEvent ()
15681 {
15682     if (gameMode == IcsExamining && !pausing) {
15683         SendToICS(ics_prefix);
15684         SendToICS("backward\n");
15685     } else {
15686         BackwardInner(currentMove - 1);
15687     }
15688 }
15689
15690 void
15691 ToStartEvent ()
15692 {
15693     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15694         /* to optimize, we temporarily turn off analysis mode while we undo
15695          * all the moves. Otherwise we get analysis output after each undo.
15696          */
15697         if (first.analysisSupport) {
15698           SendToProgram("exit\nforce\n", &first);
15699           first.analyzing = FALSE;
15700         }
15701     }
15702
15703     if (gameMode == IcsExamining && !pausing) {
15704         SendToICS(ics_prefix);
15705         SendToICS("backward 999999\n");
15706     } else {
15707         BackwardInner(backwardMostMove);
15708     }
15709
15710     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15711         /* we have fed all the moves, so reactivate analysis mode */
15712         SendToProgram("analyze\n", &first);
15713         first.analyzing = TRUE;
15714         /*first.maybeThinking = TRUE;*/
15715         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15716     }
15717 }
15718
15719 void
15720 ToNrEvent (int to)
15721 {
15722   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15723   if (to >= forwardMostMove) to = forwardMostMove;
15724   if (to <= backwardMostMove) to = backwardMostMove;
15725   if (to < currentMove) {
15726     BackwardInner(to);
15727   } else {
15728     ForwardInner(to);
15729   }
15730 }
15731
15732 void
15733 RevertEvent (Boolean annotate)
15734 {
15735     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15736         return;
15737     }
15738     if (gameMode != IcsExamining) {
15739         DisplayError(_("You are not examining a game"), 0);
15740         return;
15741     }
15742     if (pausing) {
15743         DisplayError(_("You can't revert while pausing"), 0);
15744         return;
15745     }
15746     SendToICS(ics_prefix);
15747     SendToICS("revert\n");
15748 }
15749
15750 void
15751 RetractMoveEvent ()
15752 {
15753     switch (gameMode) {
15754       case MachinePlaysWhite:
15755       case MachinePlaysBlack:
15756         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15757             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15758             return;
15759         }
15760         if (forwardMostMove < 2) return;
15761         currentMove = forwardMostMove = forwardMostMove - 2;
15762         whiteTimeRemaining = timeRemaining[0][currentMove];
15763         blackTimeRemaining = timeRemaining[1][currentMove];
15764         DisplayBothClocks();
15765         DisplayMove(currentMove - 1);
15766         ClearHighlights();/*!! could figure this out*/
15767         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15768         SendToProgram("remove\n", &first);
15769         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15770         break;
15771
15772       case BeginningOfGame:
15773       default:
15774         break;
15775
15776       case IcsPlayingWhite:
15777       case IcsPlayingBlack:
15778         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15779             SendToICS(ics_prefix);
15780             SendToICS("takeback 2\n");
15781         } else {
15782             SendToICS(ics_prefix);
15783             SendToICS("takeback 1\n");
15784         }
15785         break;
15786     }
15787 }
15788
15789 void
15790 MoveNowEvent ()
15791 {
15792     ChessProgramState *cps;
15793
15794     switch (gameMode) {
15795       case MachinePlaysWhite:
15796         if (!WhiteOnMove(forwardMostMove)) {
15797             DisplayError(_("It is your turn"), 0);
15798             return;
15799         }
15800         cps = &first;
15801         break;
15802       case MachinePlaysBlack:
15803         if (WhiteOnMove(forwardMostMove)) {
15804             DisplayError(_("It is your turn"), 0);
15805             return;
15806         }
15807         cps = &first;
15808         break;
15809       case TwoMachinesPlay:
15810         if (WhiteOnMove(forwardMostMove) ==
15811             (first.twoMachinesColor[0] == 'w')) {
15812             cps = &first;
15813         } else {
15814             cps = &second;
15815         }
15816         break;
15817       case BeginningOfGame:
15818       default:
15819         return;
15820     }
15821     SendToProgram("?\n", cps);
15822 }
15823
15824 void
15825 TruncateGameEvent ()
15826 {
15827     EditGameEvent();
15828     if (gameMode != EditGame) return;
15829     TruncateGame();
15830 }
15831
15832 void
15833 TruncateGame ()
15834 {
15835     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15836     if (forwardMostMove > currentMove) {
15837         if (gameInfo.resultDetails != NULL) {
15838             free(gameInfo.resultDetails);
15839             gameInfo.resultDetails = NULL;
15840             gameInfo.result = GameUnfinished;
15841         }
15842         forwardMostMove = currentMove;
15843         HistorySet(parseList, backwardMostMove, forwardMostMove,
15844                    currentMove-1);
15845     }
15846 }
15847
15848 void
15849 HintEvent ()
15850 {
15851     if (appData.noChessProgram) return;
15852     switch (gameMode) {
15853       case MachinePlaysWhite:
15854         if (WhiteOnMove(forwardMostMove)) {
15855             DisplayError(_("Wait until your turn."), 0);
15856             return;
15857         }
15858         break;
15859       case BeginningOfGame:
15860       case MachinePlaysBlack:
15861         if (!WhiteOnMove(forwardMostMove)) {
15862             DisplayError(_("Wait until your turn."), 0);
15863             return;
15864         }
15865         break;
15866       default:
15867         DisplayError(_("No hint available"), 0);
15868         return;
15869     }
15870     SendToProgram("hint\n", &first);
15871     hintRequested = TRUE;
15872 }
15873
15874 int
15875 SaveSelected (FILE *g, int dummy, char *dummy2)
15876 {
15877     ListGame * lg = (ListGame *) gameList.head;
15878     int nItem, cnt=0;
15879     FILE *f;
15880
15881     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15882         DisplayError(_("Game list not loaded or empty"), 0);
15883         return 0;
15884     }
15885
15886     creatingBook = TRUE; // suppresses stuff during load game
15887
15888     /* Get list size */
15889     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15890         if(lg->position >= 0) { // selected?
15891             LoadGame(f, nItem, "", TRUE);
15892             SaveGamePGN2(g); // leaves g open
15893             cnt++; DoEvents();
15894         }
15895         lg = (ListGame *) lg->node.succ;
15896     }
15897
15898     fclose(g);
15899     creatingBook = FALSE;
15900
15901     return cnt;
15902 }
15903
15904 void
15905 CreateBookEvent ()
15906 {
15907     ListGame * lg = (ListGame *) gameList.head;
15908     FILE *f, *g;
15909     int nItem;
15910     static int secondTime = FALSE;
15911
15912     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15913         DisplayError(_("Game list not loaded or empty"), 0);
15914         return;
15915     }
15916
15917     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15918         fclose(g);
15919         secondTime++;
15920         DisplayNote(_("Book file exists! Try again for overwrite."));
15921         return;
15922     }
15923
15924     creatingBook = TRUE;
15925     secondTime = FALSE;
15926
15927     /* Get list size */
15928     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15929         if(lg->position >= 0) {
15930             LoadGame(f, nItem, "", TRUE);
15931             AddGameToBook(TRUE);
15932             DoEvents();
15933         }
15934         lg = (ListGame *) lg->node.succ;
15935     }
15936
15937     creatingBook = FALSE;
15938     FlushBook();
15939 }
15940
15941 void
15942 BookEvent ()
15943 {
15944     if (appData.noChessProgram) return;
15945     switch (gameMode) {
15946       case MachinePlaysWhite:
15947         if (WhiteOnMove(forwardMostMove)) {
15948             DisplayError(_("Wait until your turn."), 0);
15949             return;
15950         }
15951         break;
15952       case BeginningOfGame:
15953       case MachinePlaysBlack:
15954         if (!WhiteOnMove(forwardMostMove)) {
15955             DisplayError(_("Wait until your turn."), 0);
15956             return;
15957         }
15958         break;
15959       case EditPosition:
15960         EditPositionDone(TRUE);
15961         break;
15962       case TwoMachinesPlay:
15963         return;
15964       default:
15965         break;
15966     }
15967     SendToProgram("bk\n", &first);
15968     bookOutput[0] = NULLCHAR;
15969     bookRequested = TRUE;
15970 }
15971
15972 void
15973 AboutGameEvent ()
15974 {
15975     char *tags = PGNTags(&gameInfo);
15976     TagsPopUp(tags, CmailMsg());
15977     free(tags);
15978 }
15979
15980 /* end button procedures */
15981
15982 void
15983 PrintPosition (FILE *fp, int move)
15984 {
15985     int i, j;
15986
15987     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15988         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15989             char c = PieceToChar(boards[move][i][j]);
15990             fputc(c == 'x' ? '.' : c, fp);
15991             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15992         }
15993     }
15994     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15995       fprintf(fp, "white to play\n");
15996     else
15997       fprintf(fp, "black to play\n");
15998 }
15999
16000 void
16001 PrintOpponents (FILE *fp)
16002 {
16003     if (gameInfo.white != NULL) {
16004         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16005     } else {
16006         fprintf(fp, "\n");
16007     }
16008 }
16009
16010 /* Find last component of program's own name, using some heuristics */
16011 void
16012 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16013 {
16014     char *p, *q, c;
16015     int local = (strcmp(host, "localhost") == 0);
16016     while (!local && (p = strchr(prog, ';')) != NULL) {
16017         p++;
16018         while (*p == ' ') p++;
16019         prog = p;
16020     }
16021     if (*prog == '"' || *prog == '\'') {
16022         q = strchr(prog + 1, *prog);
16023     } else {
16024         q = strchr(prog, ' ');
16025     }
16026     if (q == NULL) q = prog + strlen(prog);
16027     p = q;
16028     while (p >= prog && *p != '/' && *p != '\\') p--;
16029     p++;
16030     if(p == prog && *p == '"') p++;
16031     c = *q; *q = 0;
16032     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16033     memcpy(buf, p, q - p);
16034     buf[q - p] = NULLCHAR;
16035     if (!local) {
16036         strcat(buf, "@");
16037         strcat(buf, host);
16038     }
16039 }
16040
16041 char *
16042 TimeControlTagValue ()
16043 {
16044     char buf[MSG_SIZ];
16045     if (!appData.clockMode) {
16046       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16047     } else if (movesPerSession > 0) {
16048       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16049     } else if (timeIncrement == 0) {
16050       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16051     } else {
16052       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16053     }
16054     return StrSave(buf);
16055 }
16056
16057 void
16058 SetGameInfo ()
16059 {
16060     /* This routine is used only for certain modes */
16061     VariantClass v = gameInfo.variant;
16062     ChessMove r = GameUnfinished;
16063     char *p = NULL;
16064
16065     if(keepInfo) return;
16066
16067     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16068         r = gameInfo.result;
16069         p = gameInfo.resultDetails;
16070         gameInfo.resultDetails = NULL;
16071     }
16072     ClearGameInfo(&gameInfo);
16073     gameInfo.variant = v;
16074
16075     switch (gameMode) {
16076       case MachinePlaysWhite:
16077         gameInfo.event = StrSave( appData.pgnEventHeader );
16078         gameInfo.site = StrSave(HostName());
16079         gameInfo.date = PGNDate();
16080         gameInfo.round = StrSave("-");
16081         gameInfo.white = StrSave(first.tidy);
16082         gameInfo.black = StrSave(UserName());
16083         gameInfo.timeControl = TimeControlTagValue();
16084         break;
16085
16086       case MachinePlaysBlack:
16087         gameInfo.event = StrSave( appData.pgnEventHeader );
16088         gameInfo.site = StrSave(HostName());
16089         gameInfo.date = PGNDate();
16090         gameInfo.round = StrSave("-");
16091         gameInfo.white = StrSave(UserName());
16092         gameInfo.black = StrSave(first.tidy);
16093         gameInfo.timeControl = TimeControlTagValue();
16094         break;
16095
16096       case TwoMachinesPlay:
16097         gameInfo.event = StrSave( appData.pgnEventHeader );
16098         gameInfo.site = StrSave(HostName());
16099         gameInfo.date = PGNDate();
16100         if (roundNr > 0) {
16101             char buf[MSG_SIZ];
16102             snprintf(buf, MSG_SIZ, "%d", roundNr);
16103             gameInfo.round = StrSave(buf);
16104         } else {
16105             gameInfo.round = StrSave("-");
16106         }
16107         if (first.twoMachinesColor[0] == 'w') {
16108             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16109             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16110         } else {
16111             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16112             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16113         }
16114         gameInfo.timeControl = TimeControlTagValue();
16115         break;
16116
16117       case EditGame:
16118         gameInfo.event = StrSave("Edited game");
16119         gameInfo.site = StrSave(HostName());
16120         gameInfo.date = PGNDate();
16121         gameInfo.round = StrSave("-");
16122         gameInfo.white = StrSave("-");
16123         gameInfo.black = StrSave("-");
16124         gameInfo.result = r;
16125         gameInfo.resultDetails = p;
16126         break;
16127
16128       case EditPosition:
16129         gameInfo.event = StrSave("Edited position");
16130         gameInfo.site = StrSave(HostName());
16131         gameInfo.date = PGNDate();
16132         gameInfo.round = StrSave("-");
16133         gameInfo.white = StrSave("-");
16134         gameInfo.black = StrSave("-");
16135         break;
16136
16137       case IcsPlayingWhite:
16138       case IcsPlayingBlack:
16139       case IcsObserving:
16140       case IcsExamining:
16141         break;
16142
16143       case PlayFromGameFile:
16144         gameInfo.event = StrSave("Game from non-PGN file");
16145         gameInfo.site = StrSave(HostName());
16146         gameInfo.date = PGNDate();
16147         gameInfo.round = StrSave("-");
16148         gameInfo.white = StrSave("?");
16149         gameInfo.black = StrSave("?");
16150         break;
16151
16152       default:
16153         break;
16154     }
16155 }
16156
16157 void
16158 ReplaceComment (int index, char *text)
16159 {
16160     int len;
16161     char *p;
16162     float score;
16163
16164     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16165        pvInfoList[index-1].depth == len &&
16166        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16167        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16168     while (*text == '\n') text++;
16169     len = strlen(text);
16170     while (len > 0 && text[len - 1] == '\n') len--;
16171
16172     if (commentList[index] != NULL)
16173       free(commentList[index]);
16174
16175     if (len == 0) {
16176         commentList[index] = NULL;
16177         return;
16178     }
16179   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16180       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16181       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16182     commentList[index] = (char *) malloc(len + 2);
16183     strncpy(commentList[index], text, len);
16184     commentList[index][len] = '\n';
16185     commentList[index][len + 1] = NULLCHAR;
16186   } else {
16187     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16188     char *p;
16189     commentList[index] = (char *) malloc(len + 7);
16190     safeStrCpy(commentList[index], "{\n", 3);
16191     safeStrCpy(commentList[index]+2, text, len+1);
16192     commentList[index][len+2] = NULLCHAR;
16193     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16194     strcat(commentList[index], "\n}\n");
16195   }
16196 }
16197
16198 void
16199 CrushCRs (char *text)
16200 {
16201   char *p = text;
16202   char *q = text;
16203   char ch;
16204
16205   do {
16206     ch = *p++;
16207     if (ch == '\r') continue;
16208     *q++ = ch;
16209   } while (ch != '\0');
16210 }
16211
16212 void
16213 AppendComment (int index, char *text, Boolean addBraces)
16214 /* addBraces  tells if we should add {} */
16215 {
16216     int oldlen, len;
16217     char *old;
16218
16219 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16220     if(addBraces == 3) addBraces = 0; else // force appending literally
16221     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16222
16223     CrushCRs(text);
16224     while (*text == '\n') text++;
16225     len = strlen(text);
16226     while (len > 0 && text[len - 1] == '\n') len--;
16227     text[len] = NULLCHAR;
16228
16229     if (len == 0) return;
16230
16231     if (commentList[index] != NULL) {
16232       Boolean addClosingBrace = addBraces;
16233         old = commentList[index];
16234         oldlen = strlen(old);
16235         while(commentList[index][oldlen-1] ==  '\n')
16236           commentList[index][--oldlen] = NULLCHAR;
16237         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16238         safeStrCpy(commentList[index], old, oldlen + len + 6);
16239         free(old);
16240         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16241         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16242           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16243           while (*text == '\n') { text++; len--; }
16244           commentList[index][--oldlen] = NULLCHAR;
16245       }
16246         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16247         else          strcat(commentList[index], "\n");
16248         strcat(commentList[index], text);
16249         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16250         else          strcat(commentList[index], "\n");
16251     } else {
16252         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16253         if(addBraces)
16254           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16255         else commentList[index][0] = NULLCHAR;
16256         strcat(commentList[index], text);
16257         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16258         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16259     }
16260 }
16261
16262 static char *
16263 FindStr (char * text, char * sub_text)
16264 {
16265     char * result = strstr( text, sub_text );
16266
16267     if( result != NULL ) {
16268         result += strlen( sub_text );
16269     }
16270
16271     return result;
16272 }
16273
16274 /* [AS] Try to extract PV info from PGN comment */
16275 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16276 char *
16277 GetInfoFromComment (int index, char * text)
16278 {
16279     char * sep = text, *p;
16280
16281     if( text != NULL && index > 0 ) {
16282         int score = 0;
16283         int depth = 0;
16284         int time = -1, sec = 0, deci;
16285         char * s_eval = FindStr( text, "[%eval " );
16286         char * s_emt = FindStr( text, "[%emt " );
16287 #if 0
16288         if( s_eval != NULL || s_emt != NULL ) {
16289 #else
16290         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16291 #endif
16292             /* New style */
16293             char delim;
16294
16295             if( s_eval != NULL ) {
16296                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16297                     return text;
16298                 }
16299
16300                 if( delim != ']' ) {
16301                     return text;
16302                 }
16303             }
16304
16305             if( s_emt != NULL ) {
16306             }
16307                 return text;
16308         }
16309         else {
16310             /* We expect something like: [+|-]nnn.nn/dd */
16311             int score_lo = 0;
16312
16313             if(*text != '{') return text; // [HGM] braces: must be normal comment
16314
16315             sep = strchr( text, '/' );
16316             if( sep == NULL || sep < (text+4) ) {
16317                 return text;
16318             }
16319
16320             p = text;
16321             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16322             if(p[1] == '(') { // comment starts with PV
16323                p = strchr(p, ')'); // locate end of PV
16324                if(p == NULL || sep < p+5) return text;
16325                // at this point we have something like "{(.*) +0.23/6 ..."
16326                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16327                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16328                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16329             }
16330             time = -1; sec = -1; deci = -1;
16331             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16332                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16333                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16334                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16335                 return text;
16336             }
16337
16338             if( score_lo < 0 || score_lo >= 100 ) {
16339                 return text;
16340             }
16341
16342             if(sec >= 0) time = 600*time + 10*sec; else
16343             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16344
16345             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16346
16347             /* [HGM] PV time: now locate end of PV info */
16348             while( *++sep >= '0' && *sep <= '9'); // strip depth
16349             if(time >= 0)
16350             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16351             if(sec >= 0)
16352             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16353             if(deci >= 0)
16354             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16355             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16356         }
16357
16358         if( depth <= 0 ) {
16359             return text;
16360         }
16361
16362         if( time < 0 ) {
16363             time = -1;
16364         }
16365
16366         pvInfoList[index-1].depth = depth;
16367         pvInfoList[index-1].score = score;
16368         pvInfoList[index-1].time  = 10*time; // centi-sec
16369         if(*sep == '}') *sep = 0; else *--sep = '{';
16370         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16371     }
16372     return sep;
16373 }
16374
16375 void
16376 SendToProgram (char *message, ChessProgramState *cps)
16377 {
16378     int count, outCount, error;
16379     char buf[MSG_SIZ];
16380
16381     if (cps->pr == NoProc) return;
16382     Attention(cps);
16383
16384     if (appData.debugMode) {
16385         TimeMark now;
16386         GetTimeMark(&now);
16387         fprintf(debugFP, "%ld >%-6s: %s",
16388                 SubtractTimeMarks(&now, &programStartTime),
16389                 cps->which, message);
16390         if(serverFP)
16391             fprintf(serverFP, "%ld >%-6s: %s",
16392                 SubtractTimeMarks(&now, &programStartTime),
16393                 cps->which, message), fflush(serverFP);
16394     }
16395
16396     count = strlen(message);
16397     outCount = OutputToProcess(cps->pr, message, count, &error);
16398     if (outCount < count && !exiting
16399                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16400       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16401       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16402         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16403             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16404                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16405                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16406                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16407             } else {
16408                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16409                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16410                 gameInfo.result = res;
16411             }
16412             gameInfo.resultDetails = StrSave(buf);
16413         }
16414         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16415         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16416     }
16417 }
16418
16419 void
16420 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16421 {
16422     char *end_str;
16423     char buf[MSG_SIZ];
16424     ChessProgramState *cps = (ChessProgramState *)closure;
16425
16426     if (isr != cps->isr) return; /* Killed intentionally */
16427     if (count <= 0) {
16428         if (count == 0) {
16429             RemoveInputSource(cps->isr);
16430             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16431                     _(cps->which), cps->program);
16432             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16433             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16434                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16435                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16436                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16437                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16438                 } else {
16439                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16440                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16441                     gameInfo.result = res;
16442                 }
16443                 gameInfo.resultDetails = StrSave(buf);
16444             }
16445             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16446             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16447         } else {
16448             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16449                     _(cps->which), cps->program);
16450             RemoveInputSource(cps->isr);
16451
16452             /* [AS] Program is misbehaving badly... kill it */
16453             if( count == -2 ) {
16454                 DestroyChildProcess( cps->pr, 9 );
16455                 cps->pr = NoProc;
16456             }
16457
16458             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16459         }
16460         return;
16461     }
16462
16463     if ((end_str = strchr(message, '\r')) != NULL)
16464       *end_str = NULLCHAR;
16465     if ((end_str = strchr(message, '\n')) != NULL)
16466       *end_str = NULLCHAR;
16467
16468     if (appData.debugMode) {
16469         TimeMark now; int print = 1;
16470         char *quote = ""; char c; int i;
16471
16472         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16473                 char start = message[0];
16474                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16475                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16476                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16477                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16478                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16479                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16480                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16481                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16482                    sscanf(message, "hint: %c", &c)!=1 &&
16483                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16484                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16485                     print = (appData.engineComments >= 2);
16486                 }
16487                 message[0] = start; // restore original message
16488         }
16489         if(print) {
16490                 GetTimeMark(&now);
16491                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16492                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16493                         quote,
16494                         message);
16495                 if(serverFP)
16496                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16497                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16498                         quote,
16499                         message), fflush(serverFP);
16500         }
16501     }
16502
16503     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16504     if (appData.icsEngineAnalyze) {
16505         if (strstr(message, "whisper") != NULL ||
16506              strstr(message, "kibitz") != NULL ||
16507             strstr(message, "tellics") != NULL) return;
16508     }
16509
16510     HandleMachineMove(message, cps);
16511 }
16512
16513
16514 void
16515 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16516 {
16517     char buf[MSG_SIZ];
16518     int seconds;
16519
16520     if( timeControl_2 > 0 ) {
16521         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16522             tc = timeControl_2;
16523         }
16524     }
16525     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16526     inc /= cps->timeOdds;
16527     st  /= cps->timeOdds;
16528
16529     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16530
16531     if (st > 0) {
16532       /* Set exact time per move, normally using st command */
16533       if (cps->stKludge) {
16534         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16535         seconds = st % 60;
16536         if (seconds == 0) {
16537           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16538         } else {
16539           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16540         }
16541       } else {
16542         snprintf(buf, MSG_SIZ, "st %d\n", st);
16543       }
16544     } else {
16545       /* Set conventional or incremental time control, using level command */
16546       if (seconds == 0) {
16547         /* Note old gnuchess bug -- minutes:seconds used to not work.
16548            Fixed in later versions, but still avoid :seconds
16549            when seconds is 0. */
16550         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16551       } else {
16552         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16553                  seconds, inc/1000.);
16554       }
16555     }
16556     SendToProgram(buf, cps);
16557
16558     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16559     /* Orthogonally, limit search to given depth */
16560     if (sd > 0) {
16561       if (cps->sdKludge) {
16562         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16563       } else {
16564         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16565       }
16566       SendToProgram(buf, cps);
16567     }
16568
16569     if(cps->nps >= 0) { /* [HGM] nps */
16570         if(cps->supportsNPS == FALSE)
16571           cps->nps = -1; // don't use if engine explicitly says not supported!
16572         else {
16573           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16574           SendToProgram(buf, cps);
16575         }
16576     }
16577 }
16578
16579 ChessProgramState *
16580 WhitePlayer ()
16581 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16582 {
16583     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16584        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16585         return &second;
16586     return &first;
16587 }
16588
16589 void
16590 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16591 {
16592     char message[MSG_SIZ];
16593     long time, otime;
16594
16595     /* Note: this routine must be called when the clocks are stopped
16596        or when they have *just* been set or switched; otherwise
16597        it will be off by the time since the current tick started.
16598     */
16599     if (machineWhite) {
16600         time = whiteTimeRemaining / 10;
16601         otime = blackTimeRemaining / 10;
16602     } else {
16603         time = blackTimeRemaining / 10;
16604         otime = whiteTimeRemaining / 10;
16605     }
16606     /* [HGM] translate opponent's time by time-odds factor */
16607     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16608
16609     if (time <= 0) time = 1;
16610     if (otime <= 0) otime = 1;
16611
16612     snprintf(message, MSG_SIZ, "time %ld\n", time);
16613     SendToProgram(message, cps);
16614
16615     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16616     SendToProgram(message, cps);
16617 }
16618
16619 char *
16620 EngineDefinedVariant (ChessProgramState *cps, int n)
16621 {   // return name of n-th unknown variant that engine supports
16622     static char buf[MSG_SIZ];
16623     char *p, *s = cps->variants;
16624     if(!s) return NULL;
16625     do { // parse string from variants feature
16626       VariantClass v;
16627         p = strchr(s, ',');
16628         if(p) *p = NULLCHAR;
16629       v = StringToVariant(s);
16630       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16631         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16632             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16633                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16634                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16635                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16636             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16637         }
16638         if(p) *p++ = ',';
16639         if(n < 0) return buf;
16640     } while(s = p);
16641     return NULL;
16642 }
16643
16644 int
16645 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16646 {
16647   char buf[MSG_SIZ];
16648   int len = strlen(name);
16649   int val;
16650
16651   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16652     (*p) += len + 1;
16653     sscanf(*p, "%d", &val);
16654     *loc = (val != 0);
16655     while (**p && **p != ' ')
16656       (*p)++;
16657     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16658     SendToProgram(buf, cps);
16659     return TRUE;
16660   }
16661   return FALSE;
16662 }
16663
16664 int
16665 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16666 {
16667   char buf[MSG_SIZ];
16668   int len = strlen(name);
16669   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16670     (*p) += len + 1;
16671     sscanf(*p, "%d", loc);
16672     while (**p && **p != ' ') (*p)++;
16673     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16674     SendToProgram(buf, cps);
16675     return TRUE;
16676   }
16677   return FALSE;
16678 }
16679
16680 int
16681 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16682 {
16683   char buf[MSG_SIZ];
16684   int len = strlen(name);
16685   if (strncmp((*p), name, len) == 0
16686       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16687     (*p) += len + 2;
16688     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16689     sscanf(*p, "%[^\"]", *loc);
16690     while (**p && **p != '\"') (*p)++;
16691     if (**p == '\"') (*p)++;
16692     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16693     SendToProgram(buf, cps);
16694     return TRUE;
16695   }
16696   return FALSE;
16697 }
16698
16699 int
16700 ParseOption (Option *opt, ChessProgramState *cps)
16701 // [HGM] options: process the string that defines an engine option, and determine
16702 // name, type, default value, and allowed value range
16703 {
16704         char *p, *q, buf[MSG_SIZ];
16705         int n, min = (-1)<<31, max = 1<<31, def;
16706
16707         if(p = strstr(opt->name, " -spin ")) {
16708             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16709             if(max < min) max = min; // enforce consistency
16710             if(def < min) def = min;
16711             if(def > max) def = max;
16712             opt->value = def;
16713             opt->min = min;
16714             opt->max = max;
16715             opt->type = Spin;
16716         } else if((p = strstr(opt->name, " -slider "))) {
16717             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16718             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16719             if(max < min) max = min; // enforce consistency
16720             if(def < min) def = min;
16721             if(def > max) def = max;
16722             opt->value = def;
16723             opt->min = min;
16724             opt->max = max;
16725             opt->type = Spin; // Slider;
16726         } else if((p = strstr(opt->name, " -string "))) {
16727             opt->textValue = p+9;
16728             opt->type = TextBox;
16729         } else if((p = strstr(opt->name, " -file "))) {
16730             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16731             opt->textValue = p+7;
16732             opt->type = FileName; // FileName;
16733         } else if((p = strstr(opt->name, " -path "))) {
16734             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16735             opt->textValue = p+7;
16736             opt->type = PathName; // PathName;
16737         } else if(p = strstr(opt->name, " -check ")) {
16738             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16739             opt->value = (def != 0);
16740             opt->type = CheckBox;
16741         } else if(p = strstr(opt->name, " -combo ")) {
16742             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16743             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16744             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16745             opt->value = n = 0;
16746             while(q = StrStr(q, " /// ")) {
16747                 n++; *q = 0;    // count choices, and null-terminate each of them
16748                 q += 5;
16749                 if(*q == '*') { // remember default, which is marked with * prefix
16750                     q++;
16751                     opt->value = n;
16752                 }
16753                 cps->comboList[cps->comboCnt++] = q;
16754             }
16755             cps->comboList[cps->comboCnt++] = NULL;
16756             opt->max = n + 1;
16757             opt->type = ComboBox;
16758         } else if(p = strstr(opt->name, " -button")) {
16759             opt->type = Button;
16760         } else if(p = strstr(opt->name, " -save")) {
16761             opt->type = SaveButton;
16762         } else return FALSE;
16763         *p = 0; // terminate option name
16764         // now look if the command-line options define a setting for this engine option.
16765         if(cps->optionSettings && cps->optionSettings[0])
16766             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16767         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16768           snprintf(buf, MSG_SIZ, "option %s", p);
16769                 if(p = strstr(buf, ",")) *p = 0;
16770                 if(q = strchr(buf, '=')) switch(opt->type) {
16771                     case ComboBox:
16772                         for(n=0; n<opt->max; n++)
16773                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16774                         break;
16775                     case TextBox:
16776                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16777                         break;
16778                     case Spin:
16779                     case CheckBox:
16780                         opt->value = atoi(q+1);
16781                     default:
16782                         break;
16783                 }
16784                 strcat(buf, "\n");
16785                 SendToProgram(buf, cps);
16786         }
16787         return TRUE;
16788 }
16789
16790 void
16791 FeatureDone (ChessProgramState *cps, int val)
16792 {
16793   DelayedEventCallback cb = GetDelayedEvent();
16794   if ((cb == InitBackEnd3 && cps == &first) ||
16795       (cb == SettingsMenuIfReady && cps == &second) ||
16796       (cb == LoadEngine) ||
16797       (cb == TwoMachinesEventIfReady)) {
16798     CancelDelayedEvent();
16799     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16800   }
16801   cps->initDone = val;
16802   if(val) cps->reload = FALSE;
16803 }
16804
16805 /* Parse feature command from engine */
16806 void
16807 ParseFeatures (char *args, ChessProgramState *cps)
16808 {
16809   char *p = args;
16810   char *q = NULL;
16811   int val;
16812   char buf[MSG_SIZ];
16813
16814   for (;;) {
16815     while (*p == ' ') p++;
16816     if (*p == NULLCHAR) return;
16817
16818     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16819     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16820     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16821     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16822     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16823     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16824     if (BoolFeature(&p, "reuse", &val, cps)) {
16825       /* Engine can disable reuse, but can't enable it if user said no */
16826       if (!val) cps->reuse = FALSE;
16827       continue;
16828     }
16829     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16830     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16831       if (gameMode == TwoMachinesPlay) {
16832         DisplayTwoMachinesTitle();
16833       } else {
16834         DisplayTitle("");
16835       }
16836       continue;
16837     }
16838     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16839     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16840     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16841     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16842     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16843     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16844     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16845     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16846     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16847     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16848     if (IntFeature(&p, "done", &val, cps)) {
16849       FeatureDone(cps, val);
16850       continue;
16851     }
16852     /* Added by Tord: */
16853     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16854     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16855     /* End of additions by Tord */
16856
16857     /* [HGM] added features: */
16858     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16859     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16860     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16861     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16862     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16863     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16864     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16865     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16866         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16867         FREE(cps->option[cps->nrOptions].name);
16868         cps->option[cps->nrOptions].name = q; q = NULL;
16869         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16870           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16871             SendToProgram(buf, cps);
16872             continue;
16873         }
16874         if(cps->nrOptions >= MAX_OPTIONS) {
16875             cps->nrOptions--;
16876             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16877             DisplayError(buf, 0);
16878         }
16879         continue;
16880     }
16881     /* End of additions by HGM */
16882
16883     /* unknown feature: complain and skip */
16884     q = p;
16885     while (*q && *q != '=') q++;
16886     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16887     SendToProgram(buf, cps);
16888     p = q;
16889     if (*p == '=') {
16890       p++;
16891       if (*p == '\"') {
16892         p++;
16893         while (*p && *p != '\"') p++;
16894         if (*p == '\"') p++;
16895       } else {
16896         while (*p && *p != ' ') p++;
16897       }
16898     }
16899   }
16900
16901 }
16902
16903 void
16904 PeriodicUpdatesEvent (int newState)
16905 {
16906     if (newState == appData.periodicUpdates)
16907       return;
16908
16909     appData.periodicUpdates=newState;
16910
16911     /* Display type changes, so update it now */
16912 //    DisplayAnalysis();
16913
16914     /* Get the ball rolling again... */
16915     if (newState) {
16916         AnalysisPeriodicEvent(1);
16917         StartAnalysisClock();
16918     }
16919 }
16920
16921 void
16922 PonderNextMoveEvent (int newState)
16923 {
16924     if (newState == appData.ponderNextMove) return;
16925     if (gameMode == EditPosition) EditPositionDone(TRUE);
16926     if (newState) {
16927         SendToProgram("hard\n", &first);
16928         if (gameMode == TwoMachinesPlay) {
16929             SendToProgram("hard\n", &second);
16930         }
16931     } else {
16932         SendToProgram("easy\n", &first);
16933         thinkOutput[0] = NULLCHAR;
16934         if (gameMode == TwoMachinesPlay) {
16935             SendToProgram("easy\n", &second);
16936         }
16937     }
16938     appData.ponderNextMove = newState;
16939 }
16940
16941 void
16942 NewSettingEvent (int option, int *feature, char *command, int value)
16943 {
16944     char buf[MSG_SIZ];
16945
16946     if (gameMode == EditPosition) EditPositionDone(TRUE);
16947     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16948     if(feature == NULL || *feature) SendToProgram(buf, &first);
16949     if (gameMode == TwoMachinesPlay) {
16950         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16951     }
16952 }
16953
16954 void
16955 ShowThinkingEvent ()
16956 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16957 {
16958     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16959     int newState = appData.showThinking
16960         // [HGM] thinking: other features now need thinking output as well
16961         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16962
16963     if (oldState == newState) return;
16964     oldState = newState;
16965     if (gameMode == EditPosition) EditPositionDone(TRUE);
16966     if (oldState) {
16967         SendToProgram("post\n", &first);
16968         if (gameMode == TwoMachinesPlay) {
16969             SendToProgram("post\n", &second);
16970         }
16971     } else {
16972         SendToProgram("nopost\n", &first);
16973         thinkOutput[0] = NULLCHAR;
16974         if (gameMode == TwoMachinesPlay) {
16975             SendToProgram("nopost\n", &second);
16976         }
16977     }
16978 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16979 }
16980
16981 void
16982 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16983 {
16984   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16985   if (pr == NoProc) return;
16986   AskQuestion(title, question, replyPrefix, pr);
16987 }
16988
16989 void
16990 TypeInEvent (char firstChar)
16991 {
16992     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16993         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16994         gameMode == AnalyzeMode || gameMode == EditGame ||
16995         gameMode == EditPosition || gameMode == IcsExamining ||
16996         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16997         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16998                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16999                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17000         gameMode == Training) PopUpMoveDialog(firstChar);
17001 }
17002
17003 void
17004 TypeInDoneEvent (char *move)
17005 {
17006         Board board;
17007         int n, fromX, fromY, toX, toY;
17008         char promoChar;
17009         ChessMove moveType;
17010
17011         // [HGM] FENedit
17012         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17013                 EditPositionPasteFEN(move);
17014                 return;
17015         }
17016         // [HGM] movenum: allow move number to be typed in any mode
17017         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17018           ToNrEvent(2*n-1);
17019           return;
17020         }
17021         // undocumented kludge: allow command-line option to be typed in!
17022         // (potentially fatal, and does not implement the effect of the option.)
17023         // should only be used for options that are values on which future decisions will be made,
17024         // and definitely not on options that would be used during initialization.
17025         if(strstr(move, "!!! -") == move) {
17026             ParseArgsFromString(move+4);
17027             return;
17028         }
17029
17030       if (gameMode != EditGame && currentMove != forwardMostMove &&
17031         gameMode != Training) {
17032         DisplayMoveError(_("Displayed move is not current"));
17033       } else {
17034         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17035           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17036         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17037         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17038           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17039           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17040         } else {
17041           DisplayMoveError(_("Could not parse move"));
17042         }
17043       }
17044 }
17045
17046 void
17047 DisplayMove (int moveNumber)
17048 {
17049     char message[MSG_SIZ];
17050     char res[MSG_SIZ];
17051     char cpThinkOutput[MSG_SIZ];
17052
17053     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17054
17055     if (moveNumber == forwardMostMove - 1 ||
17056         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17057
17058         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17059
17060         if (strchr(cpThinkOutput, '\n')) {
17061             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17062         }
17063     } else {
17064         *cpThinkOutput = NULLCHAR;
17065     }
17066
17067     /* [AS] Hide thinking from human user */
17068     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17069         *cpThinkOutput = NULLCHAR;
17070         if( thinkOutput[0] != NULLCHAR ) {
17071             int i;
17072
17073             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17074                 cpThinkOutput[i] = '.';
17075             }
17076             cpThinkOutput[i] = NULLCHAR;
17077             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17078         }
17079     }
17080
17081     if (moveNumber == forwardMostMove - 1 &&
17082         gameInfo.resultDetails != NULL) {
17083         if (gameInfo.resultDetails[0] == NULLCHAR) {
17084           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17085         } else {
17086           snprintf(res, MSG_SIZ, " {%s} %s",
17087                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17088         }
17089     } else {
17090         res[0] = NULLCHAR;
17091     }
17092
17093     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17094         DisplayMessage(res, cpThinkOutput);
17095     } else {
17096       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17097                 WhiteOnMove(moveNumber) ? " " : ".. ",
17098                 parseList[moveNumber], res);
17099         DisplayMessage(message, cpThinkOutput);
17100     }
17101 }
17102
17103 void
17104 DisplayComment (int moveNumber, char *text)
17105 {
17106     char title[MSG_SIZ];
17107
17108     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17109       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17110     } else {
17111       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17112               WhiteOnMove(moveNumber) ? " " : ".. ",
17113               parseList[moveNumber]);
17114     }
17115     if (text != NULL && (appData.autoDisplayComment || commentUp))
17116         CommentPopUp(title, text);
17117 }
17118
17119 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17120  * might be busy thinking or pondering.  It can be omitted if your
17121  * gnuchess is configured to stop thinking immediately on any user
17122  * input.  However, that gnuchess feature depends on the FIONREAD
17123  * ioctl, which does not work properly on some flavors of Unix.
17124  */
17125 void
17126 Attention (ChessProgramState *cps)
17127 {
17128 #if ATTENTION
17129     if (!cps->useSigint) return;
17130     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17131     switch (gameMode) {
17132       case MachinePlaysWhite:
17133       case MachinePlaysBlack:
17134       case TwoMachinesPlay:
17135       case IcsPlayingWhite:
17136       case IcsPlayingBlack:
17137       case AnalyzeMode:
17138       case AnalyzeFile:
17139         /* Skip if we know it isn't thinking */
17140         if (!cps->maybeThinking) return;
17141         if (appData.debugMode)
17142           fprintf(debugFP, "Interrupting %s\n", cps->which);
17143         InterruptChildProcess(cps->pr);
17144         cps->maybeThinking = FALSE;
17145         break;
17146       default:
17147         break;
17148     }
17149 #endif /*ATTENTION*/
17150 }
17151
17152 int
17153 CheckFlags ()
17154 {
17155     if (whiteTimeRemaining <= 0) {
17156         if (!whiteFlag) {
17157             whiteFlag = TRUE;
17158             if (appData.icsActive) {
17159                 if (appData.autoCallFlag &&
17160                     gameMode == IcsPlayingBlack && !blackFlag) {
17161                   SendToICS(ics_prefix);
17162                   SendToICS("flag\n");
17163                 }
17164             } else {
17165                 if (blackFlag) {
17166                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17167                 } else {
17168                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17169                     if (appData.autoCallFlag) {
17170                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17171                         return TRUE;
17172                     }
17173                 }
17174             }
17175         }
17176     }
17177     if (blackTimeRemaining <= 0) {
17178         if (!blackFlag) {
17179             blackFlag = TRUE;
17180             if (appData.icsActive) {
17181                 if (appData.autoCallFlag &&
17182                     gameMode == IcsPlayingWhite && !whiteFlag) {
17183                   SendToICS(ics_prefix);
17184                   SendToICS("flag\n");
17185                 }
17186             } else {
17187                 if (whiteFlag) {
17188                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17189                 } else {
17190                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17191                     if (appData.autoCallFlag) {
17192                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17193                         return TRUE;
17194                     }
17195                 }
17196             }
17197         }
17198     }
17199     return FALSE;
17200 }
17201
17202 void
17203 CheckTimeControl ()
17204 {
17205     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17206         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17207
17208     /*
17209      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17210      */
17211     if ( !WhiteOnMove(forwardMostMove) ) {
17212         /* White made time control */
17213         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17214         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17215         /* [HGM] time odds: correct new time quota for time odds! */
17216                                             / WhitePlayer()->timeOdds;
17217         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17218     } else {
17219         lastBlack -= blackTimeRemaining;
17220         /* Black made time control */
17221         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17222                                             / WhitePlayer()->other->timeOdds;
17223         lastWhite = whiteTimeRemaining;
17224     }
17225 }
17226
17227 void
17228 DisplayBothClocks ()
17229 {
17230     int wom = gameMode == EditPosition ?
17231       !blackPlaysFirst : WhiteOnMove(currentMove);
17232     DisplayWhiteClock(whiteTimeRemaining, wom);
17233     DisplayBlackClock(blackTimeRemaining, !wom);
17234 }
17235
17236
17237 /* Timekeeping seems to be a portability nightmare.  I think everyone
17238    has ftime(), but I'm really not sure, so I'm including some ifdefs
17239    to use other calls if you don't.  Clocks will be less accurate if
17240    you have neither ftime nor gettimeofday.
17241 */
17242
17243 /* VS 2008 requires the #include outside of the function */
17244 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17245 #include <sys/timeb.h>
17246 #endif
17247
17248 /* Get the current time as a TimeMark */
17249 void
17250 GetTimeMark (TimeMark *tm)
17251 {
17252 #if HAVE_GETTIMEOFDAY
17253
17254     struct timeval timeVal;
17255     struct timezone timeZone;
17256
17257     gettimeofday(&timeVal, &timeZone);
17258     tm->sec = (long) timeVal.tv_sec;
17259     tm->ms = (int) (timeVal.tv_usec / 1000L);
17260
17261 #else /*!HAVE_GETTIMEOFDAY*/
17262 #if HAVE_FTIME
17263
17264 // include <sys/timeb.h> / moved to just above start of function
17265     struct timeb timeB;
17266
17267     ftime(&timeB);
17268     tm->sec = (long) timeB.time;
17269     tm->ms = (int) timeB.millitm;
17270
17271 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17272     tm->sec = (long) time(NULL);
17273     tm->ms = 0;
17274 #endif
17275 #endif
17276 }
17277
17278 /* Return the difference in milliseconds between two
17279    time marks.  We assume the difference will fit in a long!
17280 */
17281 long
17282 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17283 {
17284     return 1000L*(tm2->sec - tm1->sec) +
17285            (long) (tm2->ms - tm1->ms);
17286 }
17287
17288
17289 /*
17290  * Code to manage the game clocks.
17291  *
17292  * In tournament play, black starts the clock and then white makes a move.
17293  * We give the human user a slight advantage if he is playing white---the
17294  * clocks don't run until he makes his first move, so it takes zero time.
17295  * Also, we don't account for network lag, so we could get out of sync
17296  * with GNU Chess's clock -- but then, referees are always right.
17297  */
17298
17299 static TimeMark tickStartTM;
17300 static long intendedTickLength;
17301
17302 long
17303 NextTickLength (long timeRemaining)
17304 {
17305     long nominalTickLength, nextTickLength;
17306
17307     if (timeRemaining > 0L && timeRemaining <= 10000L)
17308       nominalTickLength = 100L;
17309     else
17310       nominalTickLength = 1000L;
17311     nextTickLength = timeRemaining % nominalTickLength;
17312     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17313
17314     return nextTickLength;
17315 }
17316
17317 /* Adjust clock one minute up or down */
17318 void
17319 AdjustClock (Boolean which, int dir)
17320 {
17321     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17322     if(which) blackTimeRemaining += 60000*dir;
17323     else      whiteTimeRemaining += 60000*dir;
17324     DisplayBothClocks();
17325     adjustedClock = TRUE;
17326 }
17327
17328 /* Stop clocks and reset to a fresh time control */
17329 void
17330 ResetClocks ()
17331 {
17332     (void) StopClockTimer();
17333     if (appData.icsActive) {
17334         whiteTimeRemaining = blackTimeRemaining = 0;
17335     } else if (searchTime) {
17336         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17337         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17338     } else { /* [HGM] correct new time quote for time odds */
17339         whiteTC = blackTC = fullTimeControlString;
17340         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17341         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17342     }
17343     if (whiteFlag || blackFlag) {
17344         DisplayTitle("");
17345         whiteFlag = blackFlag = FALSE;
17346     }
17347     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17348     DisplayBothClocks();
17349     adjustedClock = FALSE;
17350 }
17351
17352 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17353
17354 /* Decrement running clock by amount of time that has passed */
17355 void
17356 DecrementClocks ()
17357 {
17358     long timeRemaining;
17359     long lastTickLength, fudge;
17360     TimeMark now;
17361
17362     if (!appData.clockMode) return;
17363     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17364
17365     GetTimeMark(&now);
17366
17367     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17368
17369     /* Fudge if we woke up a little too soon */
17370     fudge = intendedTickLength - lastTickLength;
17371     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17372
17373     if (WhiteOnMove(forwardMostMove)) {
17374         if(whiteNPS >= 0) lastTickLength = 0;
17375         timeRemaining = whiteTimeRemaining -= lastTickLength;
17376         if(timeRemaining < 0 && !appData.icsActive) {
17377             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17378             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17379                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17380                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17381             }
17382         }
17383         DisplayWhiteClock(whiteTimeRemaining - fudge,
17384                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17385     } else {
17386         if(blackNPS >= 0) lastTickLength = 0;
17387         timeRemaining = blackTimeRemaining -= lastTickLength;
17388         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17389             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17390             if(suddenDeath) {
17391                 blackStartMove = forwardMostMove;
17392                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17393             }
17394         }
17395         DisplayBlackClock(blackTimeRemaining - fudge,
17396                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17397     }
17398     if (CheckFlags()) return;
17399
17400     if(twoBoards) { // count down secondary board's clocks as well
17401         activePartnerTime -= lastTickLength;
17402         partnerUp = 1;
17403         if(activePartner == 'W')
17404             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17405         else
17406             DisplayBlackClock(activePartnerTime, TRUE);
17407         partnerUp = 0;
17408     }
17409
17410     tickStartTM = now;
17411     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17412     StartClockTimer(intendedTickLength);
17413
17414     /* if the time remaining has fallen below the alarm threshold, sound the
17415      * alarm. if the alarm has sounded and (due to a takeback or time control
17416      * with increment) the time remaining has increased to a level above the
17417      * threshold, reset the alarm so it can sound again.
17418      */
17419
17420     if (appData.icsActive && appData.icsAlarm) {
17421
17422         /* make sure we are dealing with the user's clock */
17423         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17424                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17425            )) return;
17426
17427         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17428             alarmSounded = FALSE;
17429         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17430             PlayAlarmSound();
17431             alarmSounded = TRUE;
17432         }
17433     }
17434 }
17435
17436
17437 /* A player has just moved, so stop the previously running
17438    clock and (if in clock mode) start the other one.
17439    We redisplay both clocks in case we're in ICS mode, because
17440    ICS gives us an update to both clocks after every move.
17441    Note that this routine is called *after* forwardMostMove
17442    is updated, so the last fractional tick must be subtracted
17443    from the color that is *not* on move now.
17444 */
17445 void
17446 SwitchClocks (int newMoveNr)
17447 {
17448     long lastTickLength;
17449     TimeMark now;
17450     int flagged = FALSE;
17451
17452     GetTimeMark(&now);
17453
17454     if (StopClockTimer() && appData.clockMode) {
17455         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17456         if (!WhiteOnMove(forwardMostMove)) {
17457             if(blackNPS >= 0) lastTickLength = 0;
17458             blackTimeRemaining -= lastTickLength;
17459            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17460 //         if(pvInfoList[forwardMostMove].time == -1)
17461                  pvInfoList[forwardMostMove].time =               // use GUI time
17462                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17463         } else {
17464            if(whiteNPS >= 0) lastTickLength = 0;
17465            whiteTimeRemaining -= lastTickLength;
17466            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17467 //         if(pvInfoList[forwardMostMove].time == -1)
17468                  pvInfoList[forwardMostMove].time =
17469                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17470         }
17471         flagged = CheckFlags();
17472     }
17473     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17474     CheckTimeControl();
17475
17476     if (flagged || !appData.clockMode) return;
17477
17478     switch (gameMode) {
17479       case MachinePlaysBlack:
17480       case MachinePlaysWhite:
17481       case BeginningOfGame:
17482         if (pausing) return;
17483         break;
17484
17485       case EditGame:
17486       case PlayFromGameFile:
17487       case IcsExamining:
17488         return;
17489
17490       default:
17491         break;
17492     }
17493
17494     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17495         if(WhiteOnMove(forwardMostMove))
17496              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17497         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17498     }
17499
17500     tickStartTM = now;
17501     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17502       whiteTimeRemaining : blackTimeRemaining);
17503     StartClockTimer(intendedTickLength);
17504 }
17505
17506
17507 /* Stop both clocks */
17508 void
17509 StopClocks ()
17510 {
17511     long lastTickLength;
17512     TimeMark now;
17513
17514     if (!StopClockTimer()) return;
17515     if (!appData.clockMode) return;
17516
17517     GetTimeMark(&now);
17518
17519     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17520     if (WhiteOnMove(forwardMostMove)) {
17521         if(whiteNPS >= 0) lastTickLength = 0;
17522         whiteTimeRemaining -= lastTickLength;
17523         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17524     } else {
17525         if(blackNPS >= 0) lastTickLength = 0;
17526         blackTimeRemaining -= lastTickLength;
17527         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17528     }
17529     CheckFlags();
17530 }
17531
17532 /* Start clock of player on move.  Time may have been reset, so
17533    if clock is already running, stop and restart it. */
17534 void
17535 StartClocks ()
17536 {
17537     (void) StopClockTimer(); /* in case it was running already */
17538     DisplayBothClocks();
17539     if (CheckFlags()) return;
17540
17541     if (!appData.clockMode) return;
17542     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17543
17544     GetTimeMark(&tickStartTM);
17545     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17546       whiteTimeRemaining : blackTimeRemaining);
17547
17548    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17549     whiteNPS = blackNPS = -1;
17550     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17551        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17552         whiteNPS = first.nps;
17553     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17554        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17555         blackNPS = first.nps;
17556     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17557         whiteNPS = second.nps;
17558     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17559         blackNPS = second.nps;
17560     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17561
17562     StartClockTimer(intendedTickLength);
17563 }
17564
17565 char *
17566 TimeString (long ms)
17567 {
17568     long second, minute, hour, day;
17569     char *sign = "";
17570     static char buf[32];
17571
17572     if (ms > 0 && ms <= 9900) {
17573       /* convert milliseconds to tenths, rounding up */
17574       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17575
17576       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17577       return buf;
17578     }
17579
17580     /* convert milliseconds to seconds, rounding up */
17581     /* use floating point to avoid strangeness of integer division
17582        with negative dividends on many machines */
17583     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17584
17585     if (second < 0) {
17586         sign = "-";
17587         second = -second;
17588     }
17589
17590     day = second / (60 * 60 * 24);
17591     second = second % (60 * 60 * 24);
17592     hour = second / (60 * 60);
17593     second = second % (60 * 60);
17594     minute = second / 60;
17595     second = second % 60;
17596
17597     if (day > 0)
17598       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17599               sign, day, hour, minute, second);
17600     else if (hour > 0)
17601       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17602     else
17603       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17604
17605     return buf;
17606 }
17607
17608
17609 /*
17610  * This is necessary because some C libraries aren't ANSI C compliant yet.
17611  */
17612 char *
17613 StrStr (char *string, char *match)
17614 {
17615     int i, length;
17616
17617     length = strlen(match);
17618
17619     for (i = strlen(string) - length; i >= 0; i--, string++)
17620       if (!strncmp(match, string, length))
17621         return string;
17622
17623     return NULL;
17624 }
17625
17626 char *
17627 StrCaseStr (char *string, char *match)
17628 {
17629     int i, j, length;
17630
17631     length = strlen(match);
17632
17633     for (i = strlen(string) - length; i >= 0; i--, string++) {
17634         for (j = 0; j < length; j++) {
17635             if (ToLower(match[j]) != ToLower(string[j]))
17636               break;
17637         }
17638         if (j == length) return string;
17639     }
17640
17641     return NULL;
17642 }
17643
17644 #ifndef _amigados
17645 int
17646 StrCaseCmp (char *s1, char *s2)
17647 {
17648     char c1, c2;
17649
17650     for (;;) {
17651         c1 = ToLower(*s1++);
17652         c2 = ToLower(*s2++);
17653         if (c1 > c2) return 1;
17654         if (c1 < c2) return -1;
17655         if (c1 == NULLCHAR) return 0;
17656     }
17657 }
17658
17659
17660 int
17661 ToLower (int c)
17662 {
17663     return isupper(c) ? tolower(c) : c;
17664 }
17665
17666
17667 int
17668 ToUpper (int c)
17669 {
17670     return islower(c) ? toupper(c) : c;
17671 }
17672 #endif /* !_amigados    */
17673
17674 char *
17675 StrSave (char *s)
17676 {
17677   char *ret;
17678
17679   if ((ret = (char *) malloc(strlen(s) + 1)))
17680     {
17681       safeStrCpy(ret, s, strlen(s)+1);
17682     }
17683   return ret;
17684 }
17685
17686 char *
17687 StrSavePtr (char *s, char **savePtr)
17688 {
17689     if (*savePtr) {
17690         free(*savePtr);
17691     }
17692     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17693       safeStrCpy(*savePtr, s, strlen(s)+1);
17694     }
17695     return(*savePtr);
17696 }
17697
17698 char *
17699 PGNDate ()
17700 {
17701     time_t clock;
17702     struct tm *tm;
17703     char buf[MSG_SIZ];
17704
17705     clock = time((time_t *)NULL);
17706     tm = localtime(&clock);
17707     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17708             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17709     return StrSave(buf);
17710 }
17711
17712
17713 char *
17714 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17715 {
17716     int i, j, fromX, fromY, toX, toY;
17717     int whiteToPlay;
17718     char buf[MSG_SIZ];
17719     char *p, *q;
17720     int emptycount;
17721     ChessSquare piece;
17722
17723     whiteToPlay = (gameMode == EditPosition) ?
17724       !blackPlaysFirst : (move % 2 == 0);
17725     p = buf;
17726
17727     /* Piece placement data */
17728     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17729         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17730         emptycount = 0;
17731         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17732             if (boards[move][i][j] == EmptySquare) {
17733                 emptycount++;
17734             } else { ChessSquare piece = boards[move][i][j];
17735                 if (emptycount > 0) {
17736                     if(emptycount<10) /* [HGM] can be >= 10 */
17737                         *p++ = '0' + emptycount;
17738                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17739                     emptycount = 0;
17740                 }
17741                 if(PieceToChar(piece) == '+') {
17742                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17743                     *p++ = '+';
17744                     piece = (ChessSquare)(CHUDEMOTED piece);
17745                 }
17746                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17747                 if(p[-1] == '~') {
17748                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17749                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17750                     *p++ = '~';
17751                 }
17752             }
17753         }
17754         if (emptycount > 0) {
17755             if(emptycount<10) /* [HGM] can be >= 10 */
17756                 *p++ = '0' + emptycount;
17757             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17758             emptycount = 0;
17759         }
17760         *p++ = '/';
17761     }
17762     *(p - 1) = ' ';
17763
17764     /* [HGM] print Crazyhouse or Shogi holdings */
17765     if( gameInfo.holdingsWidth ) {
17766         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17767         q = p;
17768         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17769             piece = boards[move][i][BOARD_WIDTH-1];
17770             if( piece != EmptySquare )
17771               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17772                   *p++ = PieceToChar(piece);
17773         }
17774         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17775             piece = boards[move][BOARD_HEIGHT-i-1][0];
17776             if( piece != EmptySquare )
17777               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17778                   *p++ = PieceToChar(piece);
17779         }
17780
17781         if( q == p ) *p++ = '-';
17782         *p++ = ']';
17783         *p++ = ' ';
17784     }
17785
17786     /* Active color */
17787     *p++ = whiteToPlay ? 'w' : 'b';
17788     *p++ = ' ';
17789
17790   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17791     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17792   } else {
17793   if(nrCastlingRights) {
17794      q = p;
17795      if(appData.fischerCastling) {
17796        /* [HGM] write directly from rights */
17797            if(boards[move][CASTLING][2] != NoRights &&
17798               boards[move][CASTLING][0] != NoRights   )
17799                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17800            if(boards[move][CASTLING][2] != NoRights &&
17801               boards[move][CASTLING][1] != NoRights   )
17802                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17803            if(boards[move][CASTLING][5] != NoRights &&
17804               boards[move][CASTLING][3] != NoRights   )
17805                 *p++ = boards[move][CASTLING][3] + AAA;
17806            if(boards[move][CASTLING][5] != NoRights &&
17807               boards[move][CASTLING][4] != NoRights   )
17808                 *p++ = boards[move][CASTLING][4] + AAA;
17809      } else {
17810
17811         /* [HGM] write true castling rights */
17812         if( nrCastlingRights == 6 ) {
17813             int q, k=0;
17814             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17815                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17816             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17817                  boards[move][CASTLING][2] != NoRights  );
17818             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17819                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17820                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17821                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17822                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17823             }
17824             if(q) *p++ = 'Q';
17825             k = 0;
17826             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17827                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17828             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17829                  boards[move][CASTLING][5] != NoRights  );
17830             if(gameInfo.variant == VariantSChess) {
17831                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17832                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17833                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17834                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17835             }
17836             if(q) *p++ = 'q';
17837         }
17838      }
17839      if (q == p) *p++ = '-'; /* No castling rights */
17840      *p++ = ' ';
17841   }
17842
17843   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17844      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17845      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17846     /* En passant target square */
17847     if (move > backwardMostMove) {
17848         fromX = moveList[move - 1][0] - AAA;
17849         fromY = moveList[move - 1][1] - ONE;
17850         toX = moveList[move - 1][2] - AAA;
17851         toY = moveList[move - 1][3] - ONE;
17852         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17853             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17854             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17855             fromX == toX) {
17856             /* 2-square pawn move just happened */
17857             *p++ = toX + AAA;
17858             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17859         } else {
17860             *p++ = '-';
17861         }
17862     } else if(move == backwardMostMove) {
17863         // [HGM] perhaps we should always do it like this, and forget the above?
17864         if((signed char)boards[move][EP_STATUS] >= 0) {
17865             *p++ = boards[move][EP_STATUS] + AAA;
17866             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17867         } else {
17868             *p++ = '-';
17869         }
17870     } else {
17871         *p++ = '-';
17872     }
17873     *p++ = ' ';
17874   }
17875   }
17876
17877     if(moveCounts)
17878     {   int i = 0, j=move;
17879
17880         /* [HGM] find reversible plies */
17881         if (appData.debugMode) { int k;
17882             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17883             for(k=backwardMostMove; k<=forwardMostMove; k++)
17884                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17885
17886         }
17887
17888         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17889         if( j == backwardMostMove ) i += initialRulePlies;
17890         sprintf(p, "%d ", i);
17891         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17892
17893         /* Fullmove number */
17894         sprintf(p, "%d", (move / 2) + 1);
17895     } else *--p = NULLCHAR;
17896
17897     return StrSave(buf);
17898 }
17899
17900 Boolean
17901 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17902 {
17903     int i, j, k, w=0, subst=0, shuffle=0;
17904     char *p, c;
17905     int emptycount, virgin[BOARD_FILES];
17906     ChessSquare piece;
17907
17908     p = fen;
17909
17910     /* Piece placement data */
17911     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17912         j = 0;
17913         for (;;) {
17914             if (*p == '/' || *p == ' ' || *p == '[' ) {
17915                 if(j > w) w = j;
17916                 emptycount = gameInfo.boardWidth - j;
17917                 while (emptycount--)
17918                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17919                 if (*p == '/') p++;
17920                 else if(autoSize) { // we stumbled unexpectedly into end of board
17921                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17922                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17923                     }
17924                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17925                 }
17926                 break;
17927 #if(BOARD_FILES >= 10)*0
17928             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17929                 p++; emptycount=10;
17930                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17931                 while (emptycount--)
17932                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17933 #endif
17934             } else if (*p == '*') {
17935                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17936             } else if (isdigit(*p)) {
17937                 emptycount = *p++ - '0';
17938                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17939                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17940                 while (emptycount--)
17941                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17942             } else if (*p == '<') {
17943                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17944                 else if (i != 0 || !shuffle) return FALSE;
17945                 p++;
17946             } else if (shuffle && *p == '>') {
17947                 p++; // for now ignore closing shuffle range, and assume rank-end
17948             } else if (*p == '?') {
17949                 if (j >= gameInfo.boardWidth) return FALSE;
17950                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17951                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17952             } else if (*p == '+' || isalpha(*p)) {
17953                 if (j >= gameInfo.boardWidth) return FALSE;
17954                 if(*p=='+') {
17955                     piece = CharToPiece(*++p);
17956                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17957                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17958                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17959                 } else piece = CharToPiece(*p++);
17960
17961                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17962                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17963                     piece = (ChessSquare) (PROMOTED piece);
17964                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17965                     p++;
17966                 }
17967                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17968             } else {
17969                 return FALSE;
17970             }
17971         }
17972     }
17973     while (*p == '/' || *p == ' ') p++;
17974
17975     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17976
17977     /* [HGM] by default clear Crazyhouse holdings, if present */
17978     if(gameInfo.holdingsWidth) {
17979        for(i=0; i<BOARD_HEIGHT; i++) {
17980            board[i][0]             = EmptySquare; /* black holdings */
17981            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17982            board[i][1]             = (ChessSquare) 0; /* black counts */
17983            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17984        }
17985     }
17986
17987     /* [HGM] look for Crazyhouse holdings here */
17988     while(*p==' ') p++;
17989     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17990         int swap=0, wcnt=0, bcnt=0;
17991         if(*p == '[') p++;
17992         if(*p == '<') swap++, p++;
17993         if(*p == '-' ) p++; /* empty holdings */ else {
17994             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17995             /* if we would allow FEN reading to set board size, we would   */
17996             /* have to add holdings and shift the board read so far here   */
17997             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17998                 p++;
17999                 if((int) piece >= (int) BlackPawn ) {
18000                     i = (int)piece - (int)BlackPawn;
18001                     i = PieceToNumber((ChessSquare)i);
18002                     if( i >= gameInfo.holdingsSize ) return FALSE;
18003                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18004                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18005                     bcnt++;
18006                 } else {
18007                     i = (int)piece - (int)WhitePawn;
18008                     i = PieceToNumber((ChessSquare)i);
18009                     if( i >= gameInfo.holdingsSize ) return FALSE;
18010                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18011                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18012                     wcnt++;
18013                 }
18014             }
18015             if(subst) { // substitute back-rank question marks by holdings pieces
18016                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18017                     int k, m, n = bcnt + 1;
18018                     if(board[0][j] == ClearBoard) {
18019                         if(!wcnt) return FALSE;
18020                         n = rand() % wcnt;
18021                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18022                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18023                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18024                             break;
18025                         }
18026                     }
18027                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18028                         if(!bcnt) return FALSE;
18029                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18030                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18031                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18032                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18033                             break;
18034                         }
18035                     }
18036                 }
18037                 subst = 0;
18038             }
18039         }
18040         if(*p == ']') p++;
18041     }
18042
18043     if(subst) return FALSE; // substitution requested, but no holdings
18044
18045     while(*p == ' ') p++;
18046
18047     /* Active color */
18048     c = *p++;
18049     if(appData.colorNickNames) {
18050       if( c == appData.colorNickNames[0] ) c = 'w'; else
18051       if( c == appData.colorNickNames[1] ) c = 'b';
18052     }
18053     switch (c) {
18054       case 'w':
18055         *blackPlaysFirst = FALSE;
18056         break;
18057       case 'b':
18058         *blackPlaysFirst = TRUE;
18059         break;
18060       default:
18061         return FALSE;
18062     }
18063
18064     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18065     /* return the extra info in global variiables             */
18066
18067     /* set defaults in case FEN is incomplete */
18068     board[EP_STATUS] = EP_UNKNOWN;
18069     for(i=0; i<nrCastlingRights; i++ ) {
18070         board[CASTLING][i] =
18071             appData.fischerCastling ? NoRights : initialRights[i];
18072     }   /* assume possible unless obviously impossible */
18073     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18074     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18075     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18076                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18077     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18078     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18079     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18080                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18081     FENrulePlies = 0;
18082
18083     while(*p==' ') p++;
18084     if(nrCastlingRights) {
18085       int fischer = 0;
18086       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18087       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18088           /* castling indicator present, so default becomes no castlings */
18089           for(i=0; i<nrCastlingRights; i++ ) {
18090                  board[CASTLING][i] = NoRights;
18091           }
18092       }
18093       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18094              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18095              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18096              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18097         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18098
18099         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18100             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18101             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18102         }
18103         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18104             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18105         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18106                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18107         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18108                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18109         switch(c) {
18110           case'K':
18111               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18112               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18113               board[CASTLING][2] = whiteKingFile;
18114               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18115               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18116               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18117               break;
18118           case'Q':
18119               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18120               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18121               board[CASTLING][2] = whiteKingFile;
18122               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18123               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18124               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18125               break;
18126           case'k':
18127               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18128               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18129               board[CASTLING][5] = blackKingFile;
18130               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18131               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18132               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18133               break;
18134           case'q':
18135               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18136               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18137               board[CASTLING][5] = blackKingFile;
18138               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18139               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18140               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18141           case '-':
18142               break;
18143           default: /* FRC castlings */
18144               if(c >= 'a') { /* black rights */
18145                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18146                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18147                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18148                   if(i == BOARD_RGHT) break;
18149                   board[CASTLING][5] = i;
18150                   c -= AAA;
18151                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18152                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18153                   if(c > i)
18154                       board[CASTLING][3] = c;
18155                   else
18156                       board[CASTLING][4] = c;
18157               } else { /* white rights */
18158                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18159                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18160                     if(board[0][i] == WhiteKing) break;
18161                   if(i == BOARD_RGHT) break;
18162                   board[CASTLING][2] = i;
18163                   c -= AAA - 'a' + 'A';
18164                   if(board[0][c] >= WhiteKing) break;
18165                   if(c > i)
18166                       board[CASTLING][0] = c;
18167                   else
18168                       board[CASTLING][1] = c;
18169               }
18170         }
18171       }
18172       for(i=0; i<nrCastlingRights; i++)
18173         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18174       if(gameInfo.variant == VariantSChess)
18175         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18176       if(fischer && shuffle) appData.fischerCastling = TRUE;
18177     if (appData.debugMode) {
18178         fprintf(debugFP, "FEN castling rights:");
18179         for(i=0; i<nrCastlingRights; i++)
18180         fprintf(debugFP, " %d", board[CASTLING][i]);
18181         fprintf(debugFP, "\n");
18182     }
18183
18184       while(*p==' ') p++;
18185     }
18186
18187     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18188
18189     /* read e.p. field in games that know e.p. capture */
18190     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18191        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18192        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18193       if(*p=='-') {
18194         p++; board[EP_STATUS] = EP_NONE;
18195       } else {
18196          char c = *p++ - AAA;
18197
18198          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18199          if(*p >= '0' && *p <='9') p++;
18200          board[EP_STATUS] = c;
18201       }
18202     }
18203
18204
18205     if(sscanf(p, "%d", &i) == 1) {
18206         FENrulePlies = i; /* 50-move ply counter */
18207         /* (The move number is still ignored)    */
18208     }
18209
18210     return TRUE;
18211 }
18212
18213 void
18214 EditPositionPasteFEN (char *fen)
18215 {
18216   if (fen != NULL) {
18217     Board initial_position;
18218
18219     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18220       DisplayError(_("Bad FEN position in clipboard"), 0);
18221       return ;
18222     } else {
18223       int savedBlackPlaysFirst = blackPlaysFirst;
18224       EditPositionEvent();
18225       blackPlaysFirst = savedBlackPlaysFirst;
18226       CopyBoard(boards[0], initial_position);
18227       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18228       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18229       DisplayBothClocks();
18230       DrawPosition(FALSE, boards[currentMove]);
18231     }
18232   }
18233 }
18234
18235 static char cseq[12] = "\\   ";
18236
18237 Boolean
18238 set_cont_sequence (char *new_seq)
18239 {
18240     int len;
18241     Boolean ret;
18242
18243     // handle bad attempts to set the sequence
18244         if (!new_seq)
18245                 return 0; // acceptable error - no debug
18246
18247     len = strlen(new_seq);
18248     ret = (len > 0) && (len < sizeof(cseq));
18249     if (ret)
18250       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18251     else if (appData.debugMode)
18252       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18253     return ret;
18254 }
18255
18256 /*
18257     reformat a source message so words don't cross the width boundary.  internal
18258     newlines are not removed.  returns the wrapped size (no null character unless
18259     included in source message).  If dest is NULL, only calculate the size required
18260     for the dest buffer.  lp argument indicats line position upon entry, and it's
18261     passed back upon exit.
18262 */
18263 int
18264 wrap (char *dest, char *src, int count, int width, int *lp)
18265 {
18266     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18267
18268     cseq_len = strlen(cseq);
18269     old_line = line = *lp;
18270     ansi = len = clen = 0;
18271
18272     for (i=0; i < count; i++)
18273     {
18274         if (src[i] == '\033')
18275             ansi = 1;
18276
18277         // if we hit the width, back up
18278         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18279         {
18280             // store i & len in case the word is too long
18281             old_i = i, old_len = len;
18282
18283             // find the end of the last word
18284             while (i && src[i] != ' ' && src[i] != '\n')
18285             {
18286                 i--;
18287                 len--;
18288             }
18289
18290             // word too long?  restore i & len before splitting it
18291             if ((old_i-i+clen) >= width)
18292             {
18293                 i = old_i;
18294                 len = old_len;
18295             }
18296
18297             // extra space?
18298             if (i && src[i-1] == ' ')
18299                 len--;
18300
18301             if (src[i] != ' ' && src[i] != '\n')
18302             {
18303                 i--;
18304                 if (len)
18305                     len--;
18306             }
18307
18308             // now append the newline and continuation sequence
18309             if (dest)
18310                 dest[len] = '\n';
18311             len++;
18312             if (dest)
18313                 strncpy(dest+len, cseq, cseq_len);
18314             len += cseq_len;
18315             line = cseq_len;
18316             clen = cseq_len;
18317             continue;
18318         }
18319
18320         if (dest)
18321             dest[len] = src[i];
18322         len++;
18323         if (!ansi)
18324             line++;
18325         if (src[i] == '\n')
18326             line = 0;
18327         if (src[i] == 'm')
18328             ansi = 0;
18329     }
18330     if (dest && appData.debugMode)
18331     {
18332         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18333             count, width, line, len, *lp);
18334         show_bytes(debugFP, src, count);
18335         fprintf(debugFP, "\ndest: ");
18336         show_bytes(debugFP, dest, len);
18337         fprintf(debugFP, "\n");
18338     }
18339     *lp = dest ? line : old_line;
18340
18341     return len;
18342 }
18343
18344 // [HGM] vari: routines for shelving variations
18345 Boolean modeRestore = FALSE;
18346
18347 void
18348 PushInner (int firstMove, int lastMove)
18349 {
18350         int i, j, nrMoves = lastMove - firstMove;
18351
18352         // push current tail of game on stack
18353         savedResult[storedGames] = gameInfo.result;
18354         savedDetails[storedGames] = gameInfo.resultDetails;
18355         gameInfo.resultDetails = NULL;
18356         savedFirst[storedGames] = firstMove;
18357         savedLast [storedGames] = lastMove;
18358         savedFramePtr[storedGames] = framePtr;
18359         framePtr -= nrMoves; // reserve space for the boards
18360         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18361             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18362             for(j=0; j<MOVE_LEN; j++)
18363                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18364             for(j=0; j<2*MOVE_LEN; j++)
18365                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18366             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18367             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18368             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18369             pvInfoList[firstMove+i-1].depth = 0;
18370             commentList[framePtr+i] = commentList[firstMove+i];
18371             commentList[firstMove+i] = NULL;
18372         }
18373
18374         storedGames++;
18375         forwardMostMove = firstMove; // truncate game so we can start variation
18376 }
18377
18378 void
18379 PushTail (int firstMove, int lastMove)
18380 {
18381         if(appData.icsActive) { // only in local mode
18382                 forwardMostMove = currentMove; // mimic old ICS behavior
18383                 return;
18384         }
18385         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18386
18387         PushInner(firstMove, lastMove);
18388         if(storedGames == 1) GreyRevert(FALSE);
18389         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18390 }
18391
18392 void
18393 PopInner (Boolean annotate)
18394 {
18395         int i, j, nrMoves;
18396         char buf[8000], moveBuf[20];
18397
18398         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18399         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18400         nrMoves = savedLast[storedGames] - currentMove;
18401         if(annotate) {
18402                 int cnt = 10;
18403                 if(!WhiteOnMove(currentMove))
18404                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18405                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18406                 for(i=currentMove; i<forwardMostMove; i++) {
18407                         if(WhiteOnMove(i))
18408                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18409                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18410                         strcat(buf, moveBuf);
18411                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18412                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18413                 }
18414                 strcat(buf, ")");
18415         }
18416         for(i=1; i<=nrMoves; i++) { // copy last variation back
18417             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18418             for(j=0; j<MOVE_LEN; j++)
18419                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18420             for(j=0; j<2*MOVE_LEN; j++)
18421                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18422             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18423             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18424             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18425             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18426             commentList[currentMove+i] = commentList[framePtr+i];
18427             commentList[framePtr+i] = NULL;
18428         }
18429         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18430         framePtr = savedFramePtr[storedGames];
18431         gameInfo.result = savedResult[storedGames];
18432         if(gameInfo.resultDetails != NULL) {
18433             free(gameInfo.resultDetails);
18434       }
18435         gameInfo.resultDetails = savedDetails[storedGames];
18436         forwardMostMove = currentMove + nrMoves;
18437 }
18438
18439 Boolean
18440 PopTail (Boolean annotate)
18441 {
18442         if(appData.icsActive) return FALSE; // only in local mode
18443         if(!storedGames) return FALSE; // sanity
18444         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18445
18446         PopInner(annotate);
18447         if(currentMove < forwardMostMove) ForwardEvent(); else
18448         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18449
18450         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18451         return TRUE;
18452 }
18453
18454 void
18455 CleanupTail ()
18456 {       // remove all shelved variations
18457         int i;
18458         for(i=0; i<storedGames; i++) {
18459             if(savedDetails[i])
18460                 free(savedDetails[i]);
18461             savedDetails[i] = NULL;
18462         }
18463         for(i=framePtr; i<MAX_MOVES; i++) {
18464                 if(commentList[i]) free(commentList[i]);
18465                 commentList[i] = NULL;
18466         }
18467         framePtr = MAX_MOVES-1;
18468         storedGames = 0;
18469 }
18470
18471 void
18472 LoadVariation (int index, char *text)
18473 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18474         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18475         int level = 0, move;
18476
18477         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18478         // first find outermost bracketing variation
18479         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18480             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18481                 if(*p == '{') wait = '}'; else
18482                 if(*p == '[') wait = ']'; else
18483                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18484                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18485             }
18486             if(*p == wait) wait = NULLCHAR; // closing ]} found
18487             p++;
18488         }
18489         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18490         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18491         end[1] = NULLCHAR; // clip off comment beyond variation
18492         ToNrEvent(currentMove-1);
18493         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18494         // kludge: use ParsePV() to append variation to game
18495         move = currentMove;
18496         ParsePV(start, TRUE, TRUE);
18497         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18498         ClearPremoveHighlights();
18499         CommentPopDown();
18500         ToNrEvent(currentMove+1);
18501 }
18502
18503 void
18504 LoadTheme ()
18505 {
18506     char *p, *q, buf[MSG_SIZ];
18507     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18508         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18509         ParseArgsFromString(buf);
18510         ActivateTheme(TRUE); // also redo colors
18511         return;
18512     }
18513     p = nickName;
18514     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18515     {
18516         int len;
18517         q = appData.themeNames;
18518         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18519       if(appData.useBitmaps) {
18520         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18521                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18522                 appData.liteBackTextureMode,
18523                 appData.darkBackTextureMode );
18524       } else {
18525         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18526                 Col2Text(2),   // lightSquareColor
18527                 Col2Text(3) ); // darkSquareColor
18528       }
18529       if(appData.useBorder) {
18530         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18531                 appData.border);
18532       } else {
18533         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18534       }
18535       if(appData.useFont) {
18536         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18537                 appData.renderPiecesWithFont,
18538                 appData.fontToPieceTable,
18539                 Col2Text(9),    // appData.fontBackColorWhite
18540                 Col2Text(10) ); // appData.fontForeColorBlack
18541       } else {
18542         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18543                 appData.pieceDirectory);
18544         if(!appData.pieceDirectory[0])
18545           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18546                 Col2Text(0),   // whitePieceColor
18547                 Col2Text(1) ); // blackPieceColor
18548       }
18549       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18550                 Col2Text(4),   // highlightSquareColor
18551                 Col2Text(5) ); // premoveHighlightColor
18552         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18553         if(insert != q) insert[-1] = NULLCHAR;
18554         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18555         if(q)   free(q);
18556     }
18557     ActivateTheme(FALSE);
18558 }