Fix writing of Seirawan960 virginity in FEN
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.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 + 2*border;
2721     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
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-6; i++)
6032       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6033     initialPosition[EP_STATUS] = EP_NONE;
6034     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6035     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6036     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6037          SetCharTable(pieceNickName, appData.pieceNickNames);
6038     else SetCharTable(pieceNickName, "............");
6039     pieces = FIDEArray;
6040
6041     switch (gameInfo.variant) {
6042     case VariantFischeRandom:
6043       shuffleOpenings = TRUE;
6044       appData.fischerCastling = TRUE;
6045     default:
6046       break;
6047     case VariantShatranj:
6048       pieces = ShatranjArray;
6049       nrCastlingRights = 0;
6050       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6051       break;
6052     case VariantMakruk:
6053       pieces = makrukArray;
6054       nrCastlingRights = 0;
6055       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6056       break;
6057     case VariantASEAN:
6058       pieces = aseanArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6061       break;
6062     case VariantTwoKings:
6063       pieces = twoKingsArray;
6064       break;
6065     case VariantGrand:
6066       pieces = GrandArray;
6067       nrCastlingRights = 0;
6068       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6069       gameInfo.boardWidth = 10;
6070       gameInfo.boardHeight = 10;
6071       gameInfo.holdingsSize = 7;
6072       break;
6073     case VariantCapaRandom:
6074       shuffleOpenings = TRUE;
6075       appData.fischerCastling = TRUE;
6076     case VariantCapablanca:
6077       pieces = CapablancaArray;
6078       gameInfo.boardWidth = 10;
6079       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6080       break;
6081     case VariantGothic:
6082       pieces = GothicArray;
6083       gameInfo.boardWidth = 10;
6084       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6085       break;
6086     case VariantSChess:
6087       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6088       gameInfo.holdingsSize = 7;
6089       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6090       break;
6091     case VariantJanus:
6092       pieces = JanusArray;
6093       gameInfo.boardWidth = 10;
6094       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6095       nrCastlingRights = 6;
6096         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6097         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6098         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6099         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6100         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6101         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6102       break;
6103     case VariantFalcon:
6104       pieces = FalconArray;
6105       gameInfo.boardWidth = 10;
6106       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6107       break;
6108     case VariantXiangqi:
6109       pieces = XiangqiArray;
6110       gameInfo.boardWidth  = 9;
6111       gameInfo.boardHeight = 10;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6114       break;
6115     case VariantShogi:
6116       pieces = ShogiArray;
6117       gameInfo.boardWidth  = 9;
6118       gameInfo.boardHeight = 9;
6119       gameInfo.holdingsSize = 7;
6120       nrCastlingRights = 0;
6121       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6122       break;
6123     case VariantChu:
6124       pieces = ChuArray; pieceRows = 3;
6125       gameInfo.boardWidth  = 12;
6126       gameInfo.boardHeight = 12;
6127       nrCastlingRights = 0;
6128       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6129                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6130       break;
6131     case VariantCourier:
6132       pieces = CourierArray;
6133       gameInfo.boardWidth  = 12;
6134       nrCastlingRights = 0;
6135       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6136       break;
6137     case VariantKnightmate:
6138       pieces = KnightmateArray;
6139       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6140       break;
6141     case VariantSpartan:
6142       pieces = SpartanArray;
6143       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6144       break;
6145     case VariantLion:
6146       pieces = lionArray;
6147       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6148       break;
6149     case VariantChuChess:
6150       pieces = ChuChessArray;
6151       gameInfo.boardWidth = 10;
6152       gameInfo.boardHeight = 10;
6153       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6154       break;
6155     case VariantFairy:
6156       pieces = fairyArray;
6157       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6158       break;
6159     case VariantGreat:
6160       pieces = GreatArray;
6161       gameInfo.boardWidth = 10;
6162       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6163       gameInfo.holdingsSize = 8;
6164       break;
6165     case VariantSuper:
6166       pieces = FIDEArray;
6167       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6168       gameInfo.holdingsSize = 8;
6169       startedFromSetupPosition = TRUE;
6170       break;
6171     case VariantCrazyhouse:
6172     case VariantBughouse:
6173       pieces = FIDEArray;
6174       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6175       gameInfo.holdingsSize = 5;
6176       break;
6177     case VariantWildCastle:
6178       pieces = FIDEArray;
6179       /* !!?shuffle with kings guaranteed to be on d or e file */
6180       shuffleOpenings = 1;
6181       break;
6182     case VariantNoCastle:
6183       pieces = FIDEArray;
6184       nrCastlingRights = 0;
6185       /* !!?unconstrained back-rank shuffle */
6186       shuffleOpenings = 1;
6187       break;
6188     }
6189
6190     overrule = 0;
6191     if(appData.NrFiles >= 0) {
6192         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6193         gameInfo.boardWidth = appData.NrFiles;
6194     }
6195     if(appData.NrRanks >= 0) {
6196         gameInfo.boardHeight = appData.NrRanks;
6197     }
6198     if(appData.holdingsSize >= 0) {
6199         i = appData.holdingsSize;
6200         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6201         gameInfo.holdingsSize = i;
6202     }
6203     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6204     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6205         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6206
6207     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6208     if(pawnRow < 1) pawnRow = 1;
6209     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6210        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6211     if(gameInfo.variant == VariantChu) pawnRow = 3;
6212
6213     /* User pieceToChar list overrules defaults */
6214     if(appData.pieceToCharTable != NULL)
6215         SetCharTable(pieceToChar, appData.pieceToCharTable);
6216
6217     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6218
6219         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6220             s = (ChessSquare) 0; /* account holding counts in guard band */
6221         for( i=0; i<BOARD_HEIGHT; i++ )
6222             initialPosition[i][j] = s;
6223
6224         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6225         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6226         initialPosition[pawnRow][j] = WhitePawn;
6227         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6228         if(gameInfo.variant == VariantXiangqi) {
6229             if(j&1) {
6230                 initialPosition[pawnRow][j] =
6231                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6232                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6233                    initialPosition[2][j] = WhiteCannon;
6234                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6235                 }
6236             }
6237         }
6238         if(gameInfo.variant == VariantChu) {
6239              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6240                initialPosition[pawnRow+1][j] = WhiteCobra,
6241                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6242              for(i=1; i<pieceRows; i++) {
6243                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6244                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6245              }
6246         }
6247         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6248             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6249                initialPosition[0][j] = WhiteRook;
6250                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6251             }
6252         }
6253         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6254     }
6255     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6256     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6257
6258             j=BOARD_LEFT+1;
6259             initialPosition[1][j] = WhiteBishop;
6260             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6261             j=BOARD_RGHT-2;
6262             initialPosition[1][j] = WhiteRook;
6263             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6264     }
6265
6266     if( nrCastlingRights == -1) {
6267         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6268         /*       This sets default castling rights from none to normal corners   */
6269         /* Variants with other castling rights must set them themselves above    */
6270         nrCastlingRights = 6;
6271
6272         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6273         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6274         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6275         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6276         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6277         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6278      }
6279
6280      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6281      if(gameInfo.variant == VariantGreat) { // promotion commoners
6282         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6283         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6284         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6285         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6286      }
6287      if( gameInfo.variant == VariantSChess ) {
6288       initialPosition[1][0] = BlackMarshall;
6289       initialPosition[2][0] = BlackAngel;
6290       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6291       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6292       initialPosition[1][1] = initialPosition[2][1] =
6293       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6294      }
6295   if (appData.debugMode) {
6296     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6297   }
6298     if(shuffleOpenings) {
6299         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6300         startedFromSetupPosition = TRUE;
6301     }
6302     if(startedFromPositionFile) {
6303       /* [HGM] loadPos: use PositionFile for every new game */
6304       CopyBoard(initialPosition, filePosition);
6305       for(i=0; i<nrCastlingRights; i++)
6306           initialRights[i] = filePosition[CASTLING][i];
6307       startedFromSetupPosition = TRUE;
6308     }
6309
6310     CopyBoard(boards[0], initialPosition);
6311
6312     if(oldx != gameInfo.boardWidth ||
6313        oldy != gameInfo.boardHeight ||
6314        oldv != gameInfo.variant ||
6315        oldh != gameInfo.holdingsWidth
6316                                          )
6317             InitDrawingSizes(-2 ,0);
6318
6319     oldv = gameInfo.variant;
6320     if (redraw)
6321       DrawPosition(TRUE, boards[currentMove]);
6322 }
6323
6324 void
6325 SendBoard (ChessProgramState *cps, int moveNum)
6326 {
6327     char message[MSG_SIZ];
6328
6329     if (cps->useSetboard) {
6330       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6331       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6332       SendToProgram(message, cps);
6333       free(fen);
6334
6335     } else {
6336       ChessSquare *bp;
6337       int i, j, left=0, right=BOARD_WIDTH;
6338       /* Kludge to set black to move, avoiding the troublesome and now
6339        * deprecated "black" command.
6340        */
6341       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6342         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6343
6344       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6345
6346       SendToProgram("edit\n", cps);
6347       SendToProgram("#\n", cps);
6348       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6349         bp = &boards[moveNum][i][left];
6350         for (j = left; j < right; j++, bp++) {
6351           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6352           if ((int) *bp < (int) BlackPawn) {
6353             if(j == BOARD_RGHT+1)
6354                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6355             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6356             if(message[0] == '+' || message[0] == '~') {
6357               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6358                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6359                         AAA + j, ONE + i);
6360             }
6361             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6362                 message[1] = BOARD_RGHT   - 1 - j + '1';
6363                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6364             }
6365             SendToProgram(message, cps);
6366           }
6367         }
6368       }
6369
6370       SendToProgram("c\n", cps);
6371       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6372         bp = &boards[moveNum][i][left];
6373         for (j = left; j < right; j++, bp++) {
6374           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6375           if (((int) *bp != (int) EmptySquare)
6376               && ((int) *bp >= (int) BlackPawn)) {
6377             if(j == BOARD_LEFT-2)
6378                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6379             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6380                     AAA + j, ONE + i);
6381             if(message[0] == '+' || message[0] == '~') {
6382               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6383                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6384                         AAA + j, ONE + i);
6385             }
6386             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6387                 message[1] = BOARD_RGHT   - 1 - j + '1';
6388                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6389             }
6390             SendToProgram(message, cps);
6391           }
6392         }
6393       }
6394
6395       SendToProgram(".\n", cps);
6396     }
6397     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6398 }
6399
6400 char exclusionHeader[MSG_SIZ];
6401 int exCnt, excludePtr;
6402 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6403 static Exclusion excluTab[200];
6404 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6405
6406 static void
6407 WriteMap (int s)
6408 {
6409     int j;
6410     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6411     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6412 }
6413
6414 static void
6415 ClearMap ()
6416 {
6417     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6418     excludePtr = 24; exCnt = 0;
6419     WriteMap(0);
6420 }
6421
6422 static void
6423 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6424 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6425     char buf[2*MOVE_LEN], *p;
6426     Exclusion *e = excluTab;
6427     int i;
6428     for(i=0; i<exCnt; i++)
6429         if(e[i].ff == fromX && e[i].fr == fromY &&
6430            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6431     if(i == exCnt) { // was not in exclude list; add it
6432         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6433         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6434             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6435             return; // abort
6436         }
6437         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6438         excludePtr++; e[i].mark = excludePtr++;
6439         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6440         exCnt++;
6441     }
6442     exclusionHeader[e[i].mark] = state;
6443 }
6444
6445 static int
6446 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6447 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6448     char buf[MSG_SIZ];
6449     int j, k;
6450     ChessMove moveType;
6451     if((signed char)promoChar == -1) { // kludge to indicate best move
6452         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6453             return 1; // if unparsable, abort
6454     }
6455     // update exclusion map (resolving toggle by consulting existing state)
6456     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6457     j = k%8; k >>= 3;
6458     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6459     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6460          excludeMap[k] |=   1<<j;
6461     else excludeMap[k] &= ~(1<<j);
6462     // update header
6463     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6464     // inform engine
6465     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6466     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6467     SendToBoth(buf);
6468     return (state == '+');
6469 }
6470
6471 static void
6472 ExcludeClick (int index)
6473 {
6474     int i, j;
6475     Exclusion *e = excluTab;
6476     if(index < 25) { // none, best or tail clicked
6477         if(index < 13) { // none: include all
6478             WriteMap(0); // clear map
6479             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6480             SendToBoth("include all\n"); // and inform engine
6481         } else if(index > 18) { // tail
6482             if(exclusionHeader[19] == '-') { // tail was excluded
6483                 SendToBoth("include all\n");
6484                 WriteMap(0); // clear map completely
6485                 // now re-exclude selected moves
6486                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6487                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6488             } else { // tail was included or in mixed state
6489                 SendToBoth("exclude all\n");
6490                 WriteMap(0xFF); // fill map completely
6491                 // now re-include selected moves
6492                 j = 0; // count them
6493                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6494                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6495                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6496             }
6497         } else { // best
6498             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6499         }
6500     } else {
6501         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6502             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6503             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6504             break;
6505         }
6506     }
6507 }
6508
6509 ChessSquare
6510 DefaultPromoChoice (int white)
6511 {
6512     ChessSquare result;
6513     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6514        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6515         result = WhiteFerz; // no choice
6516     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6517         result= WhiteKing; // in Suicide Q is the last thing we want
6518     else if(gameInfo.variant == VariantSpartan)
6519         result = white ? WhiteQueen : WhiteAngel;
6520     else result = WhiteQueen;
6521     if(!white) result = WHITE_TO_BLACK result;
6522     return result;
6523 }
6524
6525 static int autoQueen; // [HGM] oneclick
6526
6527 int
6528 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6529 {
6530     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6531     /* [HGM] add Shogi promotions */
6532     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6533     ChessSquare piece, partner;
6534     ChessMove moveType;
6535     Boolean premove;
6536
6537     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6538     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6539
6540     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6541       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6542         return FALSE;
6543
6544     piece = boards[currentMove][fromY][fromX];
6545     if(gameInfo.variant == VariantChu) {
6546         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6547         promotionZoneSize = BOARD_HEIGHT/3;
6548         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6549     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6550         promotionZoneSize = BOARD_HEIGHT/3;
6551         highestPromotingPiece = (int)WhiteAlfil;
6552     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6553         promotionZoneSize = 3;
6554     }
6555
6556     // Treat Lance as Pawn when it is not representing Amazon or Lance
6557     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6558         if(piece == WhiteLance) piece = WhitePawn; else
6559         if(piece == BlackLance) piece = BlackPawn;
6560     }
6561
6562     // next weed out all moves that do not touch the promotion zone at all
6563     if((int)piece >= BlackPawn) {
6564         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6565              return FALSE;
6566         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6567         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6568     } else {
6569         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6570            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6571         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6572              return FALSE;
6573     }
6574
6575     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6576
6577     // weed out mandatory Shogi promotions
6578     if(gameInfo.variant == VariantShogi) {
6579         if(piece >= BlackPawn) {
6580             if(toY == 0 && piece == BlackPawn ||
6581                toY == 0 && piece == BlackQueen ||
6582                toY <= 1 && piece == BlackKnight) {
6583                 *promoChoice = '+';
6584                 return FALSE;
6585             }
6586         } else {
6587             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6588                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6589                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6590                 *promoChoice = '+';
6591                 return FALSE;
6592             }
6593         }
6594     }
6595
6596     // weed out obviously illegal Pawn moves
6597     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6598         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6599         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6600         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6601         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6602         // note we are not allowed to test for valid (non-)capture, due to premove
6603     }
6604
6605     // we either have a choice what to promote to, or (in Shogi) whether to promote
6606     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6607        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6608         ChessSquare p=BlackFerz;  // no choice
6609         while(p < EmptySquare) {  //but make sure we use piece that exists
6610             *promoChoice = PieceToChar(p++);
6611             if(*promoChoice != '.') break;
6612         }
6613         return FALSE;
6614     }
6615     // no sense asking what we must promote to if it is going to explode...
6616     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6617         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6618         return FALSE;
6619     }
6620     // give caller the default choice even if we will not make it
6621     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6622     partner = piece; // pieces can promote if the pieceToCharTable says so
6623     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6624     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6625     if(        sweepSelect && gameInfo.variant != VariantGreat
6626                            && gameInfo.variant != VariantGrand
6627                            && gameInfo.variant != VariantSuper) return FALSE;
6628     if(autoQueen) return FALSE; // predetermined
6629
6630     // suppress promotion popup on illegal moves that are not premoves
6631     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6632               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6633     if(appData.testLegality && !premove) {
6634         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6635                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6636         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6637         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6638             return FALSE;
6639     }
6640
6641     return TRUE;
6642 }
6643
6644 int
6645 InPalace (int row, int column)
6646 {   /* [HGM] for Xiangqi */
6647     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6648          column < (BOARD_WIDTH + 4)/2 &&
6649          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6650     return FALSE;
6651 }
6652
6653 int
6654 PieceForSquare (int x, int y)
6655 {
6656   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6657      return -1;
6658   else
6659      return boards[currentMove][y][x];
6660 }
6661
6662 int
6663 OKToStartUserMove (int x, int y)
6664 {
6665     ChessSquare from_piece;
6666     int white_piece;
6667
6668     if (matchMode) return FALSE;
6669     if (gameMode == EditPosition) return TRUE;
6670
6671     if (x >= 0 && y >= 0)
6672       from_piece = boards[currentMove][y][x];
6673     else
6674       from_piece = EmptySquare;
6675
6676     if (from_piece == EmptySquare) return FALSE;
6677
6678     white_piece = (int)from_piece >= (int)WhitePawn &&
6679       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6680
6681     switch (gameMode) {
6682       case AnalyzeFile:
6683       case TwoMachinesPlay:
6684       case EndOfGame:
6685         return FALSE;
6686
6687       case IcsObserving:
6688       case IcsIdle:
6689         return FALSE;
6690
6691       case MachinePlaysWhite:
6692       case IcsPlayingBlack:
6693         if (appData.zippyPlay) return FALSE;
6694         if (white_piece) {
6695             DisplayMoveError(_("You are playing Black"));
6696             return FALSE;
6697         }
6698         break;
6699
6700       case MachinePlaysBlack:
6701       case IcsPlayingWhite:
6702         if (appData.zippyPlay) return FALSE;
6703         if (!white_piece) {
6704             DisplayMoveError(_("You are playing White"));
6705             return FALSE;
6706         }
6707         break;
6708
6709       case PlayFromGameFile:
6710             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6711       case EditGame:
6712         if (!white_piece && WhiteOnMove(currentMove)) {
6713             DisplayMoveError(_("It is White's turn"));
6714             return FALSE;
6715         }
6716         if (white_piece && !WhiteOnMove(currentMove)) {
6717             DisplayMoveError(_("It is Black's turn"));
6718             return FALSE;
6719         }
6720         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6721             /* Editing correspondence game history */
6722             /* Could disallow this or prompt for confirmation */
6723             cmailOldMove = -1;
6724         }
6725         break;
6726
6727       case BeginningOfGame:
6728         if (appData.icsActive) return FALSE;
6729         if (!appData.noChessProgram) {
6730             if (!white_piece) {
6731                 DisplayMoveError(_("You are playing White"));
6732                 return FALSE;
6733             }
6734         }
6735         break;
6736
6737       case Training:
6738         if (!white_piece && WhiteOnMove(currentMove)) {
6739             DisplayMoveError(_("It is White's turn"));
6740             return FALSE;
6741         }
6742         if (white_piece && !WhiteOnMove(currentMove)) {
6743             DisplayMoveError(_("It is Black's turn"));
6744             return FALSE;
6745         }
6746         break;
6747
6748       default:
6749       case IcsExamining:
6750         break;
6751     }
6752     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6753         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6754         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6755         && gameMode != AnalyzeFile && gameMode != Training) {
6756         DisplayMoveError(_("Displayed position is not current"));
6757         return FALSE;
6758     }
6759     return TRUE;
6760 }
6761
6762 Boolean
6763 OnlyMove (int *x, int *y, Boolean captures)
6764 {
6765     DisambiguateClosure cl;
6766     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6767     switch(gameMode) {
6768       case MachinePlaysBlack:
6769       case IcsPlayingWhite:
6770       case BeginningOfGame:
6771         if(!WhiteOnMove(currentMove)) return FALSE;
6772         break;
6773       case MachinePlaysWhite:
6774       case IcsPlayingBlack:
6775         if(WhiteOnMove(currentMove)) return FALSE;
6776         break;
6777       case EditGame:
6778         break;
6779       default:
6780         return FALSE;
6781     }
6782     cl.pieceIn = EmptySquare;
6783     cl.rfIn = *y;
6784     cl.ffIn = *x;
6785     cl.rtIn = -1;
6786     cl.ftIn = -1;
6787     cl.promoCharIn = NULLCHAR;
6788     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6789     if( cl.kind == NormalMove ||
6790         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6791         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6792         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6793       fromX = cl.ff;
6794       fromY = cl.rf;
6795       *x = cl.ft;
6796       *y = cl.rt;
6797       return TRUE;
6798     }
6799     if(cl.kind != ImpossibleMove) return FALSE;
6800     cl.pieceIn = EmptySquare;
6801     cl.rfIn = -1;
6802     cl.ffIn = -1;
6803     cl.rtIn = *y;
6804     cl.ftIn = *x;
6805     cl.promoCharIn = NULLCHAR;
6806     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6807     if( cl.kind == NormalMove ||
6808         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6809         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6810         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6811       fromX = cl.ff;
6812       fromY = cl.rf;
6813       *x = cl.ft;
6814       *y = cl.rt;
6815       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6816       return TRUE;
6817     }
6818     return FALSE;
6819 }
6820
6821 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6822 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6823 int lastLoadGameUseList = FALSE;
6824 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6825 ChessMove lastLoadGameStart = EndOfFile;
6826 int doubleClick;
6827 Boolean addToBookFlag;
6828
6829 void
6830 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6831 {
6832     ChessMove moveType;
6833     ChessSquare pup;
6834     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6835
6836     /* Check if the user is playing in turn.  This is complicated because we
6837        let the user "pick up" a piece before it is his turn.  So the piece he
6838        tried to pick up may have been captured by the time he puts it down!
6839        Therefore we use the color the user is supposed to be playing in this
6840        test, not the color of the piece that is currently on the starting
6841        square---except in EditGame mode, where the user is playing both
6842        sides; fortunately there the capture race can't happen.  (It can
6843        now happen in IcsExamining mode, but that's just too bad.  The user
6844        will get a somewhat confusing message in that case.)
6845        */
6846
6847     switch (gameMode) {
6848       case AnalyzeFile:
6849       case TwoMachinesPlay:
6850       case EndOfGame:
6851       case IcsObserving:
6852       case IcsIdle:
6853         /* We switched into a game mode where moves are not accepted,
6854            perhaps while the mouse button was down. */
6855         return;
6856
6857       case MachinePlaysWhite:
6858         /* User is moving for Black */
6859         if (WhiteOnMove(currentMove)) {
6860             DisplayMoveError(_("It is White's turn"));
6861             return;
6862         }
6863         break;
6864
6865       case MachinePlaysBlack:
6866         /* User is moving for White */
6867         if (!WhiteOnMove(currentMove)) {
6868             DisplayMoveError(_("It is Black's turn"));
6869             return;
6870         }
6871         break;
6872
6873       case PlayFromGameFile:
6874             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6875       case EditGame:
6876       case IcsExamining:
6877       case BeginningOfGame:
6878       case AnalyzeMode:
6879       case Training:
6880         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6881         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6882             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6883             /* User is moving for Black */
6884             if (WhiteOnMove(currentMove)) {
6885                 DisplayMoveError(_("It is White's turn"));
6886                 return;
6887             }
6888         } else {
6889             /* User is moving for White */
6890             if (!WhiteOnMove(currentMove)) {
6891                 DisplayMoveError(_("It is Black's turn"));
6892                 return;
6893             }
6894         }
6895         break;
6896
6897       case IcsPlayingBlack:
6898         /* User is moving for Black */
6899         if (WhiteOnMove(currentMove)) {
6900             if (!appData.premove) {
6901                 DisplayMoveError(_("It is White's turn"));
6902             } else if (toX >= 0 && toY >= 0) {
6903                 premoveToX = toX;
6904                 premoveToY = toY;
6905                 premoveFromX = fromX;
6906                 premoveFromY = fromY;
6907                 premovePromoChar = promoChar;
6908                 gotPremove = 1;
6909                 if (appData.debugMode)
6910                     fprintf(debugFP, "Got premove: fromX %d,"
6911                             "fromY %d, toX %d, toY %d\n",
6912                             fromX, fromY, toX, toY);
6913             }
6914             return;
6915         }
6916         break;
6917
6918       case IcsPlayingWhite:
6919         /* User is moving for White */
6920         if (!WhiteOnMove(currentMove)) {
6921             if (!appData.premove) {
6922                 DisplayMoveError(_("It is Black's turn"));
6923             } else if (toX >= 0 && toY >= 0) {
6924                 premoveToX = toX;
6925                 premoveToY = toY;
6926                 premoveFromX = fromX;
6927                 premoveFromY = fromY;
6928                 premovePromoChar = promoChar;
6929                 gotPremove = 1;
6930                 if (appData.debugMode)
6931                     fprintf(debugFP, "Got premove: fromX %d,"
6932                             "fromY %d, toX %d, toY %d\n",
6933                             fromX, fromY, toX, toY);
6934             }
6935             return;
6936         }
6937         break;
6938
6939       default:
6940         break;
6941
6942       case EditPosition:
6943         /* EditPosition, empty square, or different color piece;
6944            click-click move is possible */
6945         if (toX == -2 || toY == -2) {
6946             boards[0][fromY][fromX] = EmptySquare;
6947             DrawPosition(FALSE, boards[currentMove]);
6948             return;
6949         } else if (toX >= 0 && toY >= 0) {
6950             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6951                 ChessSquare q, p = boards[0][rf][ff];
6952                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6953                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6954                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6955                 if(PieceToChar(q) == '+') gatingPiece = p;
6956             }
6957             boards[0][toY][toX] = boards[0][fromY][fromX];
6958             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6959                 if(boards[0][fromY][0] != EmptySquare) {
6960                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6961                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6962                 }
6963             } else
6964             if(fromX == BOARD_RGHT+1) {
6965                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6966                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6967                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6968                 }
6969             } else
6970             boards[0][fromY][fromX] = gatingPiece;
6971             DrawPosition(FALSE, boards[currentMove]);
6972             return;
6973         }
6974         return;
6975     }
6976
6977     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6978     pup = boards[currentMove][toY][toX];
6979
6980     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6981     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6982          if( pup != EmptySquare ) return;
6983          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6984            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6985                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6986            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6987            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6988            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6989            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6990          fromY = DROP_RANK;
6991     }
6992
6993     /* [HGM] always test for legality, to get promotion info */
6994     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6995                                          fromY, fromX, toY, toX, promoChar);
6996
6997     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6998
6999     /* [HGM] but possibly ignore an IllegalMove result */
7000     if (appData.testLegality) {
7001         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7002             DisplayMoveError(_("Illegal move"));
7003             return;
7004         }
7005     }
7006
7007     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7008         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7009              ClearPremoveHighlights(); // was included
7010         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7011         return;
7012     }
7013
7014     if(addToBookFlag) { // adding moves to book
7015         char buf[MSG_SIZ], move[MSG_SIZ];
7016         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7017         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7018         AddBookMove(buf);
7019         addToBookFlag = FALSE;
7020         ClearHighlights();
7021         return;
7022     }
7023
7024     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7025 }
7026
7027 /* Common tail of UserMoveEvent and DropMenuEvent */
7028 int
7029 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7030 {
7031     char *bookHit = 0;
7032
7033     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7034         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7035         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7036         if(WhiteOnMove(currentMove)) {
7037             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7038         } else {
7039             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7040         }
7041     }
7042
7043     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7044        move type in caller when we know the move is a legal promotion */
7045     if(moveType == NormalMove && promoChar)
7046         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7047
7048     /* [HGM] <popupFix> The following if has been moved here from
7049        UserMoveEvent(). Because it seemed to belong here (why not allow
7050        piece drops in training games?), and because it can only be
7051        performed after it is known to what we promote. */
7052     if (gameMode == Training) {
7053       /* compare the move played on the board to the next move in the
7054        * game. If they match, display the move and the opponent's response.
7055        * If they don't match, display an error message.
7056        */
7057       int saveAnimate;
7058       Board testBoard;
7059       CopyBoard(testBoard, boards[currentMove]);
7060       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7061
7062       if (CompareBoards(testBoard, boards[currentMove+1])) {
7063         ForwardInner(currentMove+1);
7064
7065         /* Autoplay the opponent's response.
7066          * if appData.animate was TRUE when Training mode was entered,
7067          * the response will be animated.
7068          */
7069         saveAnimate = appData.animate;
7070         appData.animate = animateTraining;
7071         ForwardInner(currentMove+1);
7072         appData.animate = saveAnimate;
7073
7074         /* check for the end of the game */
7075         if (currentMove >= forwardMostMove) {
7076           gameMode = PlayFromGameFile;
7077           ModeHighlight();
7078           SetTrainingModeOff();
7079           DisplayInformation(_("End of game"));
7080         }
7081       } else {
7082         DisplayError(_("Incorrect move"), 0);
7083       }
7084       return 1;
7085     }
7086
7087   /* Ok, now we know that the move is good, so we can kill
7088      the previous line in Analysis Mode */
7089   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7090                                 && currentMove < forwardMostMove) {
7091     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7092     else forwardMostMove = currentMove;
7093   }
7094
7095   ClearMap();
7096
7097   /* If we need the chess program but it's dead, restart it */
7098   ResurrectChessProgram();
7099
7100   /* A user move restarts a paused game*/
7101   if (pausing)
7102     PauseEvent();
7103
7104   thinkOutput[0] = NULLCHAR;
7105
7106   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7107
7108   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7109     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7110     return 1;
7111   }
7112
7113   if (gameMode == BeginningOfGame) {
7114     if (appData.noChessProgram) {
7115       gameMode = EditGame;
7116       SetGameInfo();
7117     } else {
7118       char buf[MSG_SIZ];
7119       gameMode = MachinePlaysBlack;
7120       StartClocks();
7121       SetGameInfo();
7122       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7123       DisplayTitle(buf);
7124       if (first.sendName) {
7125         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7126         SendToProgram(buf, &first);
7127       }
7128       StartClocks();
7129     }
7130     ModeHighlight();
7131   }
7132
7133   /* Relay move to ICS or chess engine */
7134   if (appData.icsActive) {
7135     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7136         gameMode == IcsExamining) {
7137       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7138         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7139         SendToICS("draw ");
7140         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7141       }
7142       // also send plain move, in case ICS does not understand atomic claims
7143       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7144       ics_user_moved = 1;
7145     }
7146   } else {
7147     if (first.sendTime && (gameMode == BeginningOfGame ||
7148                            gameMode == MachinePlaysWhite ||
7149                            gameMode == MachinePlaysBlack)) {
7150       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7151     }
7152     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7153          // [HGM] book: if program might be playing, let it use book
7154         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7155         first.maybeThinking = TRUE;
7156     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7157         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7158         SendBoard(&first, currentMove+1);
7159         if(second.analyzing) {
7160             if(!second.useSetboard) SendToProgram("undo\n", &second);
7161             SendBoard(&second, currentMove+1);
7162         }
7163     } else {
7164         SendMoveToProgram(forwardMostMove-1, &first);
7165         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7166     }
7167     if (currentMove == cmailOldMove + 1) {
7168       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7169     }
7170   }
7171
7172   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7173
7174   switch (gameMode) {
7175   case EditGame:
7176     if(appData.testLegality)
7177     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7178     case MT_NONE:
7179     case MT_CHECK:
7180       break;
7181     case MT_CHECKMATE:
7182     case MT_STAINMATE:
7183       if (WhiteOnMove(currentMove)) {
7184         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7185       } else {
7186         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7187       }
7188       break;
7189     case MT_STALEMATE:
7190       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7191       break;
7192     }
7193     break;
7194
7195   case MachinePlaysBlack:
7196   case MachinePlaysWhite:
7197     /* disable certain menu options while machine is thinking */
7198     SetMachineThinkingEnables();
7199     break;
7200
7201   default:
7202     break;
7203   }
7204
7205   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7206   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7207
7208   if(bookHit) { // [HGM] book: simulate book reply
7209         static char bookMove[MSG_SIZ]; // a bit generous?
7210
7211         programStats.nodes = programStats.depth = programStats.time =
7212         programStats.score = programStats.got_only_move = 0;
7213         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7214
7215         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7216         strcat(bookMove, bookHit);
7217         HandleMachineMove(bookMove, &first);
7218   }
7219   return 1;
7220 }
7221
7222 void
7223 MarkByFEN(char *fen)
7224 {
7225         int r, f;
7226         if(!appData.markers || !appData.highlightDragging) return;
7227         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7228         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7229         while(*fen) {
7230             int s = 0;
7231             marker[r][f] = 0;
7232             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7233             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7234             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7235             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7236             if(*fen == 'T') marker[r][f++] = 0; else
7237             if(*fen == 'Y') marker[r][f++] = 1; else
7238             if(*fen == 'G') marker[r][f++] = 3; else
7239             if(*fen == 'B') marker[r][f++] = 4; else
7240             if(*fen == 'C') marker[r][f++] = 5; else
7241             if(*fen == 'M') marker[r][f++] = 6; else
7242             if(*fen == 'W') marker[r][f++] = 7; else
7243             if(*fen == 'D') marker[r][f++] = 8; else
7244             if(*fen == 'R') marker[r][f++] = 2; else {
7245                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7246               f += s; fen -= s>0;
7247             }
7248             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7249             if(r < 0) break;
7250             fen++;
7251         }
7252         DrawPosition(TRUE, NULL);
7253 }
7254
7255 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7256
7257 void
7258 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7259 {
7260     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7261     Markers *m = (Markers *) closure;
7262     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7263         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7264                          || kind == WhiteCapturesEnPassant
7265                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7266     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7267 }
7268
7269 static int hoverSavedValid;
7270
7271 void
7272 MarkTargetSquares (int clear)
7273 {
7274   int x, y, sum=0;
7275   if(clear) { // no reason to ever suppress clearing
7276     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7277     hoverSavedValid = 0;
7278     if(!sum) return; // nothing was cleared,no redraw needed
7279   } else {
7280     int capt = 0;
7281     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7282        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7283     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7284     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7285       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7286       if(capt)
7287       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7288     }
7289   }
7290   DrawPosition(FALSE, NULL);
7291 }
7292
7293 int
7294 Explode (Board board, int fromX, int fromY, int toX, int toY)
7295 {
7296     if(gameInfo.variant == VariantAtomic &&
7297        (board[toY][toX] != EmptySquare ||                     // capture?
7298         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7299                          board[fromY][fromX] == BlackPawn   )
7300       )) {
7301         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7302         return TRUE;
7303     }
7304     return FALSE;
7305 }
7306
7307 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7308
7309 int
7310 CanPromote (ChessSquare piece, int y)
7311 {
7312         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7313         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7314         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7315         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7316            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7317            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7318          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7319         return (piece == BlackPawn && y <= zone ||
7320                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7321                 piece == BlackLance && y == 1 ||
7322                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7323 }
7324
7325 void
7326 HoverEvent (int xPix, int yPix, int x, int y)
7327 {
7328         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7329         int r, f;
7330         if(!first.highlight) return;
7331         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7332         if(x == oldX && y == oldY) return; // only do something if we enter new square
7333         oldFromX = fromX; oldFromY = fromY;
7334         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7335           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7336             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7337           hoverSavedValid = 1;
7338         } else if(oldX != x || oldY != y) {
7339           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7340           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7341           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7342             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7343           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7344             char buf[MSG_SIZ];
7345             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7346             SendToProgram(buf, &first);
7347           }
7348           oldX = x; oldY = y;
7349 //        SetHighlights(fromX, fromY, x, y);
7350         }
7351 }
7352
7353 void ReportClick(char *action, int x, int y)
7354 {
7355         char buf[MSG_SIZ]; // Inform engine of what user does
7356         int r, f;
7357         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7358           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7359             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7360         if(!first.highlight || gameMode == EditPosition) return;
7361         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7362         SendToProgram(buf, &first);
7363 }
7364
7365 void
7366 LeftClick (ClickType clickType, int xPix, int yPix)
7367 {
7368     int x, y;
7369     Boolean saveAnimate;
7370     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7371     char promoChoice = NULLCHAR;
7372     ChessSquare piece;
7373     static TimeMark lastClickTime, prevClickTime;
7374
7375     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7376
7377     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7378
7379     if (clickType == Press) ErrorPopDown();
7380     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7381
7382     x = EventToSquare(xPix, BOARD_WIDTH);
7383     y = EventToSquare(yPix, BOARD_HEIGHT);
7384     if (!flipView && y >= 0) {
7385         y = BOARD_HEIGHT - 1 - y;
7386     }
7387     if (flipView && x >= 0) {
7388         x = BOARD_WIDTH - 1 - x;
7389     }
7390
7391     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7392         defaultPromoChoice = promoSweep;
7393         promoSweep = EmptySquare;   // terminate sweep
7394         promoDefaultAltered = TRUE;
7395         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7396     }
7397
7398     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7399         if(clickType == Release) return; // ignore upclick of click-click destination
7400         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7401         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7402         if(gameInfo.holdingsWidth &&
7403                 (WhiteOnMove(currentMove)
7404                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7405                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7406             // click in right holdings, for determining promotion piece
7407             ChessSquare p = boards[currentMove][y][x];
7408             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7409             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7410             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7411                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7412                 fromX = fromY = -1;
7413                 return;
7414             }
7415         }
7416         DrawPosition(FALSE, boards[currentMove]);
7417         return;
7418     }
7419
7420     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7421     if(clickType == Press
7422             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7423               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7424               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7425         return;
7426
7427     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7428         // could be static click on premove from-square: abort premove
7429         gotPremove = 0;
7430         ClearPremoveHighlights();
7431     }
7432
7433     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7434         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7435
7436     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7437         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7438                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7439         defaultPromoChoice = DefaultPromoChoice(side);
7440     }
7441
7442     autoQueen = appData.alwaysPromoteToQueen;
7443
7444     if (fromX == -1) {
7445       int originalY = y;
7446       gatingPiece = EmptySquare;
7447       if (clickType != Press) {
7448         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7449             DragPieceEnd(xPix, yPix); dragging = 0;
7450             DrawPosition(FALSE, NULL);
7451         }
7452         return;
7453       }
7454       doubleClick = FALSE;
7455       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7456         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7457       }
7458       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7459       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7460          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7461          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7462             /* First square */
7463             if (OKToStartUserMove(fromX, fromY)) {
7464                 second = 0;
7465                 ReportClick("lift", x, y);
7466                 MarkTargetSquares(0);
7467                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7468                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7469                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7470                     promoSweep = defaultPromoChoice;
7471                     selectFlag = 0; lastX = xPix; lastY = yPix;
7472                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7473                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7474                 }
7475                 if (appData.highlightDragging) {
7476                     SetHighlights(fromX, fromY, -1, -1);
7477                 } else {
7478                     ClearHighlights();
7479                 }
7480             } else fromX = fromY = -1;
7481             return;
7482         }
7483     }
7484
7485     /* fromX != -1 */
7486     if (clickType == Press && gameMode != EditPosition) {
7487         ChessSquare fromP;
7488         ChessSquare toP;
7489         int frc;
7490
7491         // ignore off-board to clicks
7492         if(y < 0 || x < 0) return;
7493
7494         /* Check if clicking again on the same color piece */
7495         fromP = boards[currentMove][fromY][fromX];
7496         toP = boards[currentMove][y][x];
7497         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7498         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7499            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7500              WhitePawn <= toP && toP <= WhiteKing &&
7501              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7502              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7503             (BlackPawn <= fromP && fromP <= BlackKing &&
7504              BlackPawn <= toP && toP <= BlackKing &&
7505              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7506              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7507             /* Clicked again on same color piece -- changed his mind */
7508             second = (x == fromX && y == fromY);
7509             killX = killY = -1;
7510             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7511                 second = FALSE; // first double-click rather than scond click
7512                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7513             }
7514             promoDefaultAltered = FALSE;
7515             MarkTargetSquares(1);
7516            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7517             if (appData.highlightDragging) {
7518                 SetHighlights(x, y, -1, -1);
7519             } else {
7520                 ClearHighlights();
7521             }
7522             if (OKToStartUserMove(x, y)) {
7523                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7524                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7525                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7526                  gatingPiece = boards[currentMove][fromY][fromX];
7527                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7528                 fromX = x;
7529                 fromY = y; dragging = 1;
7530                 ReportClick("lift", x, y);
7531                 MarkTargetSquares(0);
7532                 DragPieceBegin(xPix, yPix, FALSE);
7533                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7534                     promoSweep = defaultPromoChoice;
7535                     selectFlag = 0; lastX = xPix; lastY = yPix;
7536                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7537                 }
7538             }
7539            }
7540            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7541            second = FALSE;
7542         }
7543         // ignore clicks on holdings
7544         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7545     }
7546
7547     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7548         DragPieceEnd(xPix, yPix); dragging = 0;
7549         if(clearFlag) {
7550             // a deferred attempt to click-click move an empty square on top of a piece
7551             boards[currentMove][y][x] = EmptySquare;
7552             ClearHighlights();
7553             DrawPosition(FALSE, boards[currentMove]);
7554             fromX = fromY = -1; clearFlag = 0;
7555             return;
7556         }
7557         if (appData.animateDragging) {
7558             /* Undo animation damage if any */
7559             DrawPosition(FALSE, NULL);
7560         }
7561         if (second || sweepSelecting) {
7562             /* Second up/down in same square; just abort move */
7563             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7564             second = sweepSelecting = 0;
7565             fromX = fromY = -1;
7566             gatingPiece = EmptySquare;
7567             MarkTargetSquares(1);
7568             ClearHighlights();
7569             gotPremove = 0;
7570             ClearPremoveHighlights();
7571         } else {
7572             /* First upclick in same square; start click-click mode */
7573             SetHighlights(x, y, -1, -1);
7574         }
7575         return;
7576     }
7577
7578     clearFlag = 0;
7579
7580     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7581        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7582         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7583         DisplayMessage(_("only marked squares are legal"),"");
7584         DrawPosition(TRUE, NULL);
7585         return; // ignore to-click
7586     }
7587
7588     /* we now have a different from- and (possibly off-board) to-square */
7589     /* Completed move */
7590     if(!sweepSelecting) {
7591         toX = x;
7592         toY = y;
7593     }
7594
7595     piece = boards[currentMove][fromY][fromX];
7596
7597     saveAnimate = appData.animate;
7598     if (clickType == Press) {
7599         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7600         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7601             // must be Edit Position mode with empty-square selected
7602             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7603             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7604             return;
7605         }
7606         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7607             return;
7608         }
7609         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7610             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7611         } else
7612         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7613         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7614           if(appData.sweepSelect) {
7615             promoSweep = defaultPromoChoice;
7616             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7617             selectFlag = 0; lastX = xPix; lastY = yPix;
7618             Sweep(0); // Pawn that is going to promote: preview promotion piece
7619             sweepSelecting = 1;
7620             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7621             MarkTargetSquares(1);
7622           }
7623           return; // promo popup appears on up-click
7624         }
7625         /* Finish clickclick move */
7626         if (appData.animate || appData.highlightLastMove) {
7627             SetHighlights(fromX, fromY, toX, toY);
7628         } else {
7629             ClearHighlights();
7630         }
7631     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7632         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7633         if (appData.animate || appData.highlightLastMove) {
7634             SetHighlights(fromX, fromY, toX, toY);
7635         } else {
7636             ClearHighlights();
7637         }
7638     } else {
7639 #if 0
7640 // [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
7641         /* Finish drag move */
7642         if (appData.highlightLastMove) {
7643             SetHighlights(fromX, fromY, toX, toY);
7644         } else {
7645             ClearHighlights();
7646         }
7647 #endif
7648         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7649         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7650           dragging *= 2;            // flag button-less dragging if we are dragging
7651           MarkTargetSquares(1);
7652           if(x == killX && y == killY) killX = killY = -1; else {
7653             killX = x; killY = y;     //remeber this square as intermediate
7654             ReportClick("put", x, y); // and inform engine
7655             ReportClick("lift", x, y);
7656             MarkTargetSquares(0);
7657             return;
7658           }
7659         }
7660         DragPieceEnd(xPix, yPix); dragging = 0;
7661         /* Don't animate move and drag both */
7662         appData.animate = FALSE;
7663     }
7664
7665     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7666     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7667         ChessSquare piece = boards[currentMove][fromY][fromX];
7668         if(gameMode == EditPosition && piece != EmptySquare &&
7669            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7670             int n;
7671
7672             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7673                 n = PieceToNumber(piece - (int)BlackPawn);
7674                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7675                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7676                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7677             } else
7678             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7679                 n = PieceToNumber(piece);
7680                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7681                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7682                 boards[currentMove][n][BOARD_WIDTH-2]++;
7683             }
7684             boards[currentMove][fromY][fromX] = EmptySquare;
7685         }
7686         ClearHighlights();
7687         fromX = fromY = -1;
7688         MarkTargetSquares(1);
7689         DrawPosition(TRUE, boards[currentMove]);
7690         return;
7691     }
7692
7693     // off-board moves should not be highlighted
7694     if(x < 0 || y < 0) ClearHighlights();
7695     else ReportClick("put", x, y);
7696
7697     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7698
7699     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7700         SetHighlights(fromX, fromY, toX, toY);
7701         MarkTargetSquares(1);
7702         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7703             // [HGM] super: promotion to captured piece selected from holdings
7704             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7705             promotionChoice = TRUE;
7706             // kludge follows to temporarily execute move on display, without promoting yet
7707             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7708             boards[currentMove][toY][toX] = p;
7709             DrawPosition(FALSE, boards[currentMove]);
7710             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7711             boards[currentMove][toY][toX] = q;
7712             DisplayMessage("Click in holdings to choose piece", "");
7713             return;
7714         }
7715         PromotionPopUp(promoChoice);
7716     } else {
7717         int oldMove = currentMove;
7718         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7719         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7720         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7721         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7722            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7723             DrawPosition(TRUE, boards[currentMove]);
7724         MarkTargetSquares(1);
7725         fromX = fromY = -1;
7726     }
7727     appData.animate = saveAnimate;
7728     if (appData.animate || appData.animateDragging) {
7729         /* Undo animation damage if needed */
7730         DrawPosition(FALSE, NULL);
7731     }
7732 }
7733
7734 int
7735 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7736 {   // front-end-free part taken out of PieceMenuPopup
7737     int whichMenu; int xSqr, ySqr;
7738
7739     if(seekGraphUp) { // [HGM] seekgraph
7740         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7741         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7742         return -2;
7743     }
7744
7745     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7746          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7747         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7748         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7749         if(action == Press)   {
7750             originalFlip = flipView;
7751             flipView = !flipView; // temporarily flip board to see game from partners perspective
7752             DrawPosition(TRUE, partnerBoard);
7753             DisplayMessage(partnerStatus, "");
7754             partnerUp = TRUE;
7755         } else if(action == Release) {
7756             flipView = originalFlip;
7757             DrawPosition(TRUE, boards[currentMove]);
7758             partnerUp = FALSE;
7759         }
7760         return -2;
7761     }
7762
7763     xSqr = EventToSquare(x, BOARD_WIDTH);
7764     ySqr = EventToSquare(y, BOARD_HEIGHT);
7765     if (action == Release) {
7766         if(pieceSweep != EmptySquare) {
7767             EditPositionMenuEvent(pieceSweep, toX, toY);
7768             pieceSweep = EmptySquare;
7769         } else UnLoadPV(); // [HGM] pv
7770     }
7771     if (action != Press) return -2; // return code to be ignored
7772     switch (gameMode) {
7773       case IcsExamining:
7774         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7775       case EditPosition:
7776         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7777         if (xSqr < 0 || ySqr < 0) return -1;
7778         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7779         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7780         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7781         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7782         NextPiece(0);
7783         return 2; // grab
7784       case IcsObserving:
7785         if(!appData.icsEngineAnalyze) return -1;
7786       case IcsPlayingWhite:
7787       case IcsPlayingBlack:
7788         if(!appData.zippyPlay) goto noZip;
7789       case AnalyzeMode:
7790       case AnalyzeFile:
7791       case MachinePlaysWhite:
7792       case MachinePlaysBlack:
7793       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7794         if (!appData.dropMenu) {
7795           LoadPV(x, y);
7796           return 2; // flag front-end to grab mouse events
7797         }
7798         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7799            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7800       case EditGame:
7801       noZip:
7802         if (xSqr < 0 || ySqr < 0) return -1;
7803         if (!appData.dropMenu || appData.testLegality &&
7804             gameInfo.variant != VariantBughouse &&
7805             gameInfo.variant != VariantCrazyhouse) return -1;
7806         whichMenu = 1; // drop menu
7807         break;
7808       default:
7809         return -1;
7810     }
7811
7812     if (((*fromX = xSqr) < 0) ||
7813         ((*fromY = ySqr) < 0)) {
7814         *fromX = *fromY = -1;
7815         return -1;
7816     }
7817     if (flipView)
7818       *fromX = BOARD_WIDTH - 1 - *fromX;
7819     else
7820       *fromY = BOARD_HEIGHT - 1 - *fromY;
7821
7822     return whichMenu;
7823 }
7824
7825 void
7826 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7827 {
7828 //    char * hint = lastHint;
7829     FrontEndProgramStats stats;
7830
7831     stats.which = cps == &first ? 0 : 1;
7832     stats.depth = cpstats->depth;
7833     stats.nodes = cpstats->nodes;
7834     stats.score = cpstats->score;
7835     stats.time = cpstats->time;
7836     stats.pv = cpstats->movelist;
7837     stats.hint = lastHint;
7838     stats.an_move_index = 0;
7839     stats.an_move_count = 0;
7840
7841     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7842         stats.hint = cpstats->move_name;
7843         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7844         stats.an_move_count = cpstats->nr_moves;
7845     }
7846
7847     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
7848
7849     SetProgramStats( &stats );
7850 }
7851
7852 void
7853 ClearEngineOutputPane (int which)
7854 {
7855     static FrontEndProgramStats dummyStats;
7856     dummyStats.which = which;
7857     dummyStats.pv = "#";
7858     SetProgramStats( &dummyStats );
7859 }
7860
7861 #define MAXPLAYERS 500
7862
7863 char *
7864 TourneyStandings (int display)
7865 {
7866     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7867     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7868     char result, *p, *names[MAXPLAYERS];
7869
7870     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7871         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7872     names[0] = p = strdup(appData.participants);
7873     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7874
7875     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7876
7877     while(result = appData.results[nr]) {
7878         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7879         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7880         wScore = bScore = 0;
7881         switch(result) {
7882           case '+': wScore = 2; break;
7883           case '-': bScore = 2; break;
7884           case '=': wScore = bScore = 1; break;
7885           case ' ':
7886           case '*': return strdup("busy"); // tourney not finished
7887         }
7888         score[w] += wScore;
7889         score[b] += bScore;
7890         games[w]++;
7891         games[b]++;
7892         nr++;
7893     }
7894     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7895     for(w=0; w<nPlayers; w++) {
7896         bScore = -1;
7897         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7898         ranking[w] = b; points[w] = bScore; score[b] = -2;
7899     }
7900     p = malloc(nPlayers*34+1);
7901     for(w=0; w<nPlayers && w<display; w++)
7902         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7903     free(names[0]);
7904     return p;
7905 }
7906
7907 void
7908 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7909 {       // count all piece types
7910         int p, f, r;
7911         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7912         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7913         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7914                 p = board[r][f];
7915                 pCnt[p]++;
7916                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7917                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7918                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7919                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7920                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7921                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7922         }
7923 }
7924
7925 int
7926 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7927 {
7928         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7929         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7930
7931         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7932         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7933         if(myPawns == 2 && nMine == 3) // KPP
7934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7935         if(myPawns == 1 && nMine == 2) // KP
7936             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7937         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7938             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7939         if(myPawns) return FALSE;
7940         if(pCnt[WhiteRook+side])
7941             return pCnt[BlackRook-side] ||
7942                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7943                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7944                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7945         if(pCnt[WhiteCannon+side]) {
7946             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7947             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7948         }
7949         if(pCnt[WhiteKnight+side])
7950             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7951         return FALSE;
7952 }
7953
7954 int
7955 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7956 {
7957         VariantClass v = gameInfo.variant;
7958
7959         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7960         if(v == VariantShatranj) return TRUE; // always winnable through baring
7961         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7962         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7963
7964         if(v == VariantXiangqi) {
7965                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7966
7967                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7968                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7969                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7970                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7971                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7972                 if(stale) // we have at least one last-rank P plus perhaps C
7973                     return majors // KPKX
7974                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7975                 else // KCA*E*
7976                     return pCnt[WhiteFerz+side] // KCAK
7977                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7978                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7979                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7980
7981         } else if(v == VariantKnightmate) {
7982                 if(nMine == 1) return FALSE;
7983                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7984         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7985                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7986
7987                 if(nMine == 1) return FALSE; // bare King
7988                 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
7989                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7990                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7991                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7992                 if(pCnt[WhiteKnight+side])
7993                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7994                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7995                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7996                 if(nBishops)
7997                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7998                 if(pCnt[WhiteAlfil+side])
7999                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8000                 if(pCnt[WhiteWazir+side])
8001                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8002         }
8003
8004         return TRUE;
8005 }
8006
8007 int
8008 CompareWithRights (Board b1, Board b2)
8009 {
8010     int rights = 0;
8011     if(!CompareBoards(b1, b2)) return FALSE;
8012     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8013     /* compare castling rights */
8014     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8015            rights++; /* King lost rights, while rook still had them */
8016     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8017         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8018            rights++; /* but at least one rook lost them */
8019     }
8020     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8021            rights++;
8022     if( b1[CASTLING][5] != NoRights ) {
8023         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8024            rights++;
8025     }
8026     return rights == 0;
8027 }
8028
8029 int
8030 Adjudicate (ChessProgramState *cps)
8031 {       // [HGM] some adjudications useful with buggy engines
8032         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8033         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8034         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8035         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8036         int k, drop, count = 0; static int bare = 1;
8037         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8038         Boolean canAdjudicate = !appData.icsActive;
8039
8040         // most tests only when we understand the game, i.e. legality-checking on
8041             if( appData.testLegality )
8042             {   /* [HGM] Some more adjudications for obstinate engines */
8043                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8044                 static int moveCount = 6;
8045                 ChessMove result;
8046                 char *reason = NULL;
8047
8048                 /* Count what is on board. */
8049                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8050
8051                 /* Some material-based adjudications that have to be made before stalemate test */
8052                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8053                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8054                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8055                      if(canAdjudicate && appData.checkMates) {
8056                          if(engineOpponent)
8057                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8058                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8059                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8060                          return 1;
8061                      }
8062                 }
8063
8064                 /* Bare King in Shatranj (loses) or Losers (wins) */
8065                 if( nrW == 1 || nrB == 1) {
8066                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8067                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8068                      if(canAdjudicate && appData.checkMates) {
8069                          if(engineOpponent)
8070                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8071                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8072                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8073                          return 1;
8074                      }
8075                   } else
8076                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8077                   {    /* bare King */
8078                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8079                         if(canAdjudicate && appData.checkMates) {
8080                             /* but only adjudicate if adjudication enabled */
8081                             if(engineOpponent)
8082                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8083                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8084                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8085                             return 1;
8086                         }
8087                   }
8088                 } else bare = 1;
8089
8090
8091             // don't wait for engine to announce game end if we can judge ourselves
8092             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8093               case MT_CHECK:
8094                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8095                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8096                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8097                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8098                             checkCnt++;
8099                         if(checkCnt >= 2) {
8100                             reason = "Xboard adjudication: 3rd check";
8101                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8102                             break;
8103                         }
8104                     }
8105                 }
8106               case MT_NONE:
8107               default:
8108                 break;
8109               case MT_STEALMATE:
8110               case MT_STALEMATE:
8111               case MT_STAINMATE:
8112                 reason = "Xboard adjudication: Stalemate";
8113                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8114                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8115                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8116                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8117                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8118                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8119                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8120                                                                         EP_CHECKMATE : EP_WINS);
8121                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8122                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8123                 }
8124                 break;
8125               case MT_CHECKMATE:
8126                 reason = "Xboard adjudication: Checkmate";
8127                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8128                 if(gameInfo.variant == VariantShogi) {
8129                     if(forwardMostMove > backwardMostMove
8130                        && moveList[forwardMostMove-1][1] == '@'
8131                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8132                         reason = "XBoard adjudication: pawn-drop mate";
8133                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8134                     }
8135                 }
8136                 break;
8137             }
8138
8139                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8140                     case EP_STALEMATE:
8141                         result = GameIsDrawn; break;
8142                     case EP_CHECKMATE:
8143                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8144                     case EP_WINS:
8145                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8146                     default:
8147                         result = EndOfFile;
8148                 }
8149                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8150                     if(engineOpponent)
8151                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8152                     GameEnds( result, reason, GE_XBOARD );
8153                     return 1;
8154                 }
8155
8156                 /* Next absolutely insufficient mating material. */
8157                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8158                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8159                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8160
8161                      /* always flag draws, for judging claims */
8162                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8163
8164                      if(canAdjudicate && appData.materialDraws) {
8165                          /* but only adjudicate them if adjudication enabled */
8166                          if(engineOpponent) {
8167                            SendToProgram("force\n", engineOpponent); // suppress reply
8168                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8169                          }
8170                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8171                          return 1;
8172                      }
8173                 }
8174
8175                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8176                 if(gameInfo.variant == VariantXiangqi ?
8177                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8178                  : nrW + nrB == 4 &&
8179                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8180                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8181                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8182                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8183                    ) ) {
8184                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8185                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8186                           if(engineOpponent) {
8187                             SendToProgram("force\n", engineOpponent); // suppress reply
8188                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8189                           }
8190                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8191                           return 1;
8192                      }
8193                 } else moveCount = 6;
8194             }
8195
8196         // Repetition draws and 50-move rule can be applied independently of legality testing
8197
8198                 /* Check for rep-draws */
8199                 count = 0;
8200                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8201                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8202                 for(k = forwardMostMove-2;
8203                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8204                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8205                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8206                     k-=2)
8207                 {   int rights=0;
8208                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8209                         /* compare castling rights */
8210                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8211                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8212                                 rights++; /* King lost rights, while rook still had them */
8213                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8214                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8215                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8216                                    rights++; /* but at least one rook lost them */
8217                         }
8218                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8219                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8220                                 rights++;
8221                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8222                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8223                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8224                                    rights++;
8225                         }
8226                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8227                             && appData.drawRepeats > 1) {
8228                              /* adjudicate after user-specified nr of repeats */
8229                              int result = GameIsDrawn;
8230                              char *details = "XBoard adjudication: repetition draw";
8231                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8232                                 // [HGM] xiangqi: check for forbidden perpetuals
8233                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8234                                 for(m=forwardMostMove; m>k; m-=2) {
8235                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8236                                         ourPerpetual = 0; // the current mover did not always check
8237                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8238                                         hisPerpetual = 0; // the opponent did not always check
8239                                 }
8240                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8241                                                                         ourPerpetual, hisPerpetual);
8242                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8243                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8244                                     details = "Xboard adjudication: perpetual checking";
8245                                 } else
8246                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8247                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8248                                 } else
8249                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8250                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8251                                         result = BlackWins;
8252                                         details = "Xboard adjudication: repetition";
8253                                     }
8254                                 } else // it must be XQ
8255                                 // Now check for perpetual chases
8256                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8257                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8258                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8259                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8260                                         static char resdet[MSG_SIZ];
8261                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8262                                         details = resdet;
8263                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8264                                     } else
8265                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8266                                         break; // Abort repetition-checking loop.
8267                                 }
8268                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8269                              }
8270                              if(engineOpponent) {
8271                                SendToProgram("force\n", engineOpponent); // suppress reply
8272                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8273                              }
8274                              GameEnds( result, details, GE_XBOARD );
8275                              return 1;
8276                         }
8277                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8278                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8279                     }
8280                 }
8281
8282                 /* Now we test for 50-move draws. Determine ply count */
8283                 count = forwardMostMove;
8284                 /* look for last irreversble move */
8285                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8286                     count--;
8287                 /* if we hit starting position, add initial plies */
8288                 if( count == backwardMostMove )
8289                     count -= initialRulePlies;
8290                 count = forwardMostMove - count;
8291                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8292                         // adjust reversible move counter for checks in Xiangqi
8293                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8294                         if(i < backwardMostMove) i = backwardMostMove;
8295                         while(i <= forwardMostMove) {
8296                                 lastCheck = inCheck; // check evasion does not count
8297                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8298                                 if(inCheck || lastCheck) count--; // check does not count
8299                                 i++;
8300                         }
8301                 }
8302                 if( count >= 100)
8303                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8304                          /* this is used to judge if draw claims are legal */
8305                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8306                          if(engineOpponent) {
8307                            SendToProgram("force\n", engineOpponent); // suppress reply
8308                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8309                          }
8310                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8311                          return 1;
8312                 }
8313
8314                 /* if draw offer is pending, treat it as a draw claim
8315                  * when draw condition present, to allow engines a way to
8316                  * claim draws before making their move to avoid a race
8317                  * condition occurring after their move
8318                  */
8319                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8320                          char *p = NULL;
8321                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8322                              p = "Draw claim: 50-move rule";
8323                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8324                              p = "Draw claim: 3-fold repetition";
8325                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8326                              p = "Draw claim: insufficient mating material";
8327                          if( p != NULL && canAdjudicate) {
8328                              if(engineOpponent) {
8329                                SendToProgram("force\n", engineOpponent); // suppress reply
8330                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8331                              }
8332                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8333                              return 1;
8334                          }
8335                 }
8336
8337                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8338                     if(engineOpponent) {
8339                       SendToProgram("force\n", engineOpponent); // suppress reply
8340                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8341                     }
8342                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8343                     return 1;
8344                 }
8345         return 0;
8346 }
8347
8348 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8349 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8350 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8351
8352 static int
8353 BitbaseProbe ()
8354 {
8355     int pieces[10], squares[10], cnt=0, r, f, res;
8356     static int loaded;
8357     static PPROBE_EGBB probeBB;
8358     if(!appData.testLegality) return 10;
8359     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8360     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8361     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8362     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8363         ChessSquare piece = boards[forwardMostMove][r][f];
8364         int black = (piece >= BlackPawn);
8365         int type = piece - black*BlackPawn;
8366         if(piece == EmptySquare) continue;
8367         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8368         if(type == WhiteKing) type = WhiteQueen + 1;
8369         type = egbbCode[type];
8370         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8371         pieces[cnt] = type + black*6;
8372         if(++cnt > 5) return 11;
8373     }
8374     pieces[cnt] = squares[cnt] = 0;
8375     // probe EGBB
8376     if(loaded == 2) return 13; // loading failed before
8377     if(loaded == 0) {
8378         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8379         HMODULE lib;
8380         PLOAD_EGBB loadBB;
8381         loaded = 2; // prepare for failure
8382         if(!path) return 13; // no egbb installed
8383         strncpy(buf, path + 8, MSG_SIZ);
8384         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8385         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8386         lib = LoadLibrary(buf);
8387         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8388         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8389         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8390         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8391         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8392         loaded = 1; // success!
8393     }
8394     res = probeBB(forwardMostMove & 1, pieces, squares);
8395     return res > 0 ? 1 : res < 0 ? -1 : 0;
8396 }
8397
8398 char *
8399 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8400 {   // [HGM] book: this routine intercepts moves to simulate book replies
8401     char *bookHit = NULL;
8402
8403     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8404         char buf[MSG_SIZ];
8405         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8406         SendToProgram(buf, cps);
8407     }
8408     //first determine if the incoming move brings opponent into his book
8409     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8410         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8411     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8412     if(bookHit != NULL && !cps->bookSuspend) {
8413         // make sure opponent is not going to reply after receiving move to book position
8414         SendToProgram("force\n", cps);
8415         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8416     }
8417     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8418     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8419     // now arrange restart after book miss
8420     if(bookHit) {
8421         // after a book hit we never send 'go', and the code after the call to this routine
8422         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8423         char buf[MSG_SIZ], *move = bookHit;
8424         if(cps->useSAN) {
8425             int fromX, fromY, toX, toY;
8426             char promoChar;
8427             ChessMove moveType;
8428             move = buf + 30;
8429             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8430                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8431                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8432                                     PosFlags(forwardMostMove),
8433                                     fromY, fromX, toY, toX, promoChar, move);
8434             } else {
8435                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8436                 bookHit = NULL;
8437             }
8438         }
8439         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8440         SendToProgram(buf, cps);
8441         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8442     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8443         SendToProgram("go\n", cps);
8444         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8445     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8446         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8447             SendToProgram("go\n", cps);
8448         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8449     }
8450     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8451 }
8452
8453 int
8454 LoadError (char *errmess, ChessProgramState *cps)
8455 {   // unloads engine and switches back to -ncp mode if it was first
8456     if(cps->initDone) return FALSE;
8457     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8458     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8459     cps->pr = NoProc;
8460     if(cps == &first) {
8461         appData.noChessProgram = TRUE;
8462         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8463         gameMode = BeginningOfGame; ModeHighlight();
8464         SetNCPMode();
8465     }
8466     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8467     DisplayMessage("", ""); // erase waiting message
8468     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8469     return TRUE;
8470 }
8471
8472 char *savedMessage;
8473 ChessProgramState *savedState;
8474 void
8475 DeferredBookMove (void)
8476 {
8477         if(savedState->lastPing != savedState->lastPong)
8478                     ScheduleDelayedEvent(DeferredBookMove, 10);
8479         else
8480         HandleMachineMove(savedMessage, savedState);
8481 }
8482
8483 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8484 static ChessProgramState *stalledEngine;
8485 static char stashedInputMove[MSG_SIZ];
8486
8487 void
8488 HandleMachineMove (char *message, ChessProgramState *cps)
8489 {
8490     static char firstLeg[20];
8491     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8492     char realname[MSG_SIZ];
8493     int fromX, fromY, toX, toY;
8494     ChessMove moveType;
8495     char promoChar, roar;
8496     char *p, *pv=buf1;
8497     int machineWhite, oldError;
8498     char *bookHit;
8499
8500     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8501         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8502         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8503             DisplayError(_("Invalid pairing from pairing engine"), 0);
8504             return;
8505         }
8506         pairingReceived = 1;
8507         NextMatchGame();
8508         return; // Skim the pairing messages here.
8509     }
8510
8511     oldError = cps->userError; cps->userError = 0;
8512
8513 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8514     /*
8515      * Kludge to ignore BEL characters
8516      */
8517     while (*message == '\007') message++;
8518
8519     /*
8520      * [HGM] engine debug message: ignore lines starting with '#' character
8521      */
8522     if(cps->debug && *message == '#') return;
8523
8524     /*
8525      * Look for book output
8526      */
8527     if (cps == &first && bookRequested) {
8528         if (message[0] == '\t' || message[0] == ' ') {
8529             /* Part of the book output is here; append it */
8530             strcat(bookOutput, message);
8531             strcat(bookOutput, "  \n");
8532             return;
8533         } else if (bookOutput[0] != NULLCHAR) {
8534             /* All of book output has arrived; display it */
8535             char *p = bookOutput;
8536             while (*p != NULLCHAR) {
8537                 if (*p == '\t') *p = ' ';
8538                 p++;
8539             }
8540             DisplayInformation(bookOutput);
8541             bookRequested = FALSE;
8542             /* Fall through to parse the current output */
8543         }
8544     }
8545
8546     /*
8547      * Look for machine move.
8548      */
8549     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8550         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8551     {
8552         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8553             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8554             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8555             stalledEngine = cps;
8556             if(appData.ponderNextMove) { // bring opponent out of ponder
8557                 if(gameMode == TwoMachinesPlay) {
8558                     if(cps->other->pause)
8559                         PauseEngine(cps->other);
8560                     else
8561                         SendToProgram("easy\n", cps->other);
8562                 }
8563             }
8564             StopClocks();
8565             return;
8566         }
8567
8568         /* This method is only useful on engines that support ping */
8569         if (cps->lastPing != cps->lastPong) {
8570           if (gameMode == BeginningOfGame) {
8571             /* Extra move from before last new; ignore */
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8574             }
8575           } else {
8576             if (appData.debugMode) {
8577                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8578                         cps->which, gameMode);
8579             }
8580
8581             SendToProgram("undo\n", cps);
8582           }
8583           return;
8584         }
8585
8586         switch (gameMode) {
8587           case BeginningOfGame:
8588             /* Extra move from before last reset; ignore */
8589             if (appData.debugMode) {
8590                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8591             }
8592             return;
8593
8594           case EndOfGame:
8595           case IcsIdle:
8596           default:
8597             /* Extra move after we tried to stop.  The mode test is
8598                not a reliable way of detecting this problem, but it's
8599                the best we can do on engines that don't support ping.
8600             */
8601             if (appData.debugMode) {
8602                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8603                         cps->which, gameMode);
8604             }
8605             SendToProgram("undo\n", cps);
8606             return;
8607
8608           case MachinePlaysWhite:
8609           case IcsPlayingWhite:
8610             machineWhite = TRUE;
8611             break;
8612
8613           case MachinePlaysBlack:
8614           case IcsPlayingBlack:
8615             machineWhite = FALSE;
8616             break;
8617
8618           case TwoMachinesPlay:
8619             machineWhite = (cps->twoMachinesColor[0] == 'w');
8620             break;
8621         }
8622         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8623             if (appData.debugMode) {
8624                 fprintf(debugFP,
8625                         "Ignoring move out of turn by %s, gameMode %d"
8626                         ", forwardMost %d\n",
8627                         cps->which, gameMode, forwardMostMove);
8628             }
8629             return;
8630         }
8631
8632         if(cps->alphaRank) AlphaRank(machineMove, 4);
8633
8634         // [HGM] lion: (some very limited) support for Alien protocol
8635         killX = killY = -1;
8636         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8637             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8638             return;
8639         } else if(firstLeg[0]) { // there was a previous leg;
8640             // only support case where same piece makes two step (and don't even test that!)
8641             char buf[20], *p = machineMove+1, *q = buf+1, f;
8642             safeStrCpy(buf, machineMove, 20);
8643             while(isdigit(*q)) q++; // find start of to-square
8644             safeStrCpy(machineMove, firstLeg, 20);
8645             while(isdigit(*p)) p++;
8646             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8647             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8648             firstLeg[0] = NULLCHAR;
8649         }
8650
8651         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8652                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8653             /* Machine move could not be parsed; ignore it. */
8654           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8655                     machineMove, _(cps->which));
8656             DisplayMoveError(buf1);
8657             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8658                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8659             if (gameMode == TwoMachinesPlay) {
8660               GameEnds(machineWhite ? BlackWins : WhiteWins,
8661                        buf1, GE_XBOARD);
8662             }
8663             return;
8664         }
8665
8666         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8667         /* So we have to redo legality test with true e.p. status here,  */
8668         /* to make sure an illegal e.p. capture does not slip through,   */
8669         /* to cause a forfeit on a justified illegal-move complaint      */
8670         /* of the opponent.                                              */
8671         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8672            ChessMove moveType;
8673            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8674                              fromY, fromX, toY, toX, promoChar);
8675             if(moveType == IllegalMove) {
8676               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8677                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8678                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8679                            buf1, GE_XBOARD);
8680                 return;
8681            } else if(!appData.fischerCastling)
8682            /* [HGM] Kludge to handle engines that send FRC-style castling
8683               when they shouldn't (like TSCP-Gothic) */
8684            switch(moveType) {
8685              case WhiteASideCastleFR:
8686              case BlackASideCastleFR:
8687                toX+=2;
8688                currentMoveString[2]++;
8689                break;
8690              case WhiteHSideCastleFR:
8691              case BlackHSideCastleFR:
8692                toX--;
8693                currentMoveString[2]--;
8694                break;
8695              default: ; // nothing to do, but suppresses warning of pedantic compilers
8696            }
8697         }
8698         hintRequested = FALSE;
8699         lastHint[0] = NULLCHAR;
8700         bookRequested = FALSE;
8701         /* Program may be pondering now */
8702         cps->maybeThinking = TRUE;
8703         if (cps->sendTime == 2) cps->sendTime = 1;
8704         if (cps->offeredDraw) cps->offeredDraw--;
8705
8706         /* [AS] Save move info*/
8707         pvInfoList[ forwardMostMove ].score = programStats.score;
8708         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8709         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8710
8711         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8712
8713         /* Test suites abort the 'game' after one move */
8714         if(*appData.finger) {
8715            static FILE *f;
8716            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8717            if(!f) f = fopen(appData.finger, "w");
8718            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8719            else { DisplayFatalError("Bad output file", errno, 0); return; }
8720            free(fen);
8721            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8722         }
8723
8724         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8725         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8726             int count = 0;
8727
8728             while( count < adjudicateLossPlies ) {
8729                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8730
8731                 if( count & 1 ) {
8732                     score = -score; /* Flip score for winning side */
8733                 }
8734
8735                 if( score > appData.adjudicateLossThreshold ) {
8736                     break;
8737                 }
8738
8739                 count++;
8740             }
8741
8742             if( count >= adjudicateLossPlies ) {
8743                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8744
8745                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8746                     "Xboard adjudication",
8747                     GE_XBOARD );
8748
8749                 return;
8750             }
8751         }
8752
8753         if(Adjudicate(cps)) {
8754             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8755             return; // [HGM] adjudicate: for all automatic game ends
8756         }
8757
8758 #if ZIPPY
8759         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8760             first.initDone) {
8761           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8762                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8763                 SendToICS("draw ");
8764                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8765           }
8766           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8767           ics_user_moved = 1;
8768           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8769                 char buf[3*MSG_SIZ];
8770
8771                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8772                         programStats.score / 100.,
8773                         programStats.depth,
8774                         programStats.time / 100.,
8775                         (unsigned int)programStats.nodes,
8776                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8777                         programStats.movelist);
8778                 SendToICS(buf);
8779           }
8780         }
8781 #endif
8782
8783         /* [AS] Clear stats for next move */
8784         ClearProgramStats();
8785         thinkOutput[0] = NULLCHAR;
8786         hiddenThinkOutputState = 0;
8787
8788         bookHit = NULL;
8789         if (gameMode == TwoMachinesPlay) {
8790             /* [HGM] relaying draw offers moved to after reception of move */
8791             /* and interpreting offer as claim if it brings draw condition */
8792             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8793                 SendToProgram("draw\n", cps->other);
8794             }
8795             if (cps->other->sendTime) {
8796                 SendTimeRemaining(cps->other,
8797                                   cps->other->twoMachinesColor[0] == 'w');
8798             }
8799             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8800             if (firstMove && !bookHit) {
8801                 firstMove = FALSE;
8802                 if (cps->other->useColors) {
8803                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8804                 }
8805                 SendToProgram("go\n", cps->other);
8806             }
8807             cps->other->maybeThinking = TRUE;
8808         }
8809
8810         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8811
8812         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8813
8814         if (!pausing && appData.ringBellAfterMoves) {
8815             if(!roar) RingBell();
8816         }
8817
8818         /*
8819          * Reenable menu items that were disabled while
8820          * machine was thinking
8821          */
8822         if (gameMode != TwoMachinesPlay)
8823             SetUserThinkingEnables();
8824
8825         // [HGM] book: after book hit opponent has received move and is now in force mode
8826         // force the book reply into it, and then fake that it outputted this move by jumping
8827         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8828         if(bookHit) {
8829                 static char bookMove[MSG_SIZ]; // a bit generous?
8830
8831                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8832                 strcat(bookMove, bookHit);
8833                 message = bookMove;
8834                 cps = cps->other;
8835                 programStats.nodes = programStats.depth = programStats.time =
8836                 programStats.score = programStats.got_only_move = 0;
8837                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8838
8839                 if(cps->lastPing != cps->lastPong) {
8840                     savedMessage = message; // args for deferred call
8841                     savedState = cps;
8842                     ScheduleDelayedEvent(DeferredBookMove, 10);
8843                     return;
8844                 }
8845                 goto FakeBookMove;
8846         }
8847
8848         return;
8849     }
8850
8851     /* Set special modes for chess engines.  Later something general
8852      *  could be added here; for now there is just one kludge feature,
8853      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8854      *  when "xboard" is given as an interactive command.
8855      */
8856     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8857         cps->useSigint = FALSE;
8858         cps->useSigterm = FALSE;
8859     }
8860     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8861       ParseFeatures(message+8, cps);
8862       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8863     }
8864
8865     if (!strncmp(message, "setup ", 6) && 
8866         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8867           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8868                                         ) { // [HGM] allow first engine to define opening position
8869       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8870       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8871       *buf = NULLCHAR;
8872       if(sscanf(message, "setup (%s", buf) == 1) {
8873         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8874         ASSIGN(appData.pieceToCharTable, buf);
8875       }
8876       if(startedFromSetupPosition) return;
8877       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8878       if(dummy >= 3) {
8879         while(message[s] && message[s++] != ' ');
8880         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8881            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8882             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8883             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8884           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8885           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8886         }
8887       }
8888       ParseFEN(boards[0], &dummy, message+s, FALSE);
8889       DrawPosition(TRUE, boards[0]);
8890       startedFromSetupPosition = TRUE;
8891       return;
8892     }
8893     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8894       ChessSquare piece = WhitePawn;
8895       char *p=buf2;
8896       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8897       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8898       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8899       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8900                                                && gameInfo.variant != VariantFairy    ) return;
8901       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8902       piece += CharToPiece(*p) - WhitePawn;
8903       if(piece < EmptySquare) {
8904         pieceDefs = TRUE;
8905         ASSIGN(pieceDesc[piece], buf1);
8906         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8907       }
8908       return;
8909     }
8910     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8911      * want this, I was asked to put it in, and obliged.
8912      */
8913     if (!strncmp(message, "setboard ", 9)) {
8914         Board initial_position;
8915
8916         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8917
8918         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8919             DisplayError(_("Bad FEN received from engine"), 0);
8920             return ;
8921         } else {
8922            Reset(TRUE, FALSE);
8923            CopyBoard(boards[0], initial_position);
8924            initialRulePlies = FENrulePlies;
8925            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8926            else gameMode = MachinePlaysBlack;
8927            DrawPosition(FALSE, boards[currentMove]);
8928         }
8929         return;
8930     }
8931
8932     /*
8933      * Look for communication commands
8934      */
8935     if (!strncmp(message, "telluser ", 9)) {
8936         if(message[9] == '\\' && message[10] == '\\')
8937             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8938         PlayTellSound();
8939         DisplayNote(message + 9);
8940         return;
8941     }
8942     if (!strncmp(message, "tellusererror ", 14)) {
8943         cps->userError = 1;
8944         if(message[14] == '\\' && message[15] == '\\')
8945             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8946         PlayTellSound();
8947         DisplayError(message + 14, 0);
8948         return;
8949     }
8950     if (!strncmp(message, "tellopponent ", 13)) {
8951       if (appData.icsActive) {
8952         if (loggedOn) {
8953           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8954           SendToICS(buf1);
8955         }
8956       } else {
8957         DisplayNote(message + 13);
8958       }
8959       return;
8960     }
8961     if (!strncmp(message, "tellothers ", 11)) {
8962       if (appData.icsActive) {
8963         if (loggedOn) {
8964           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8965           SendToICS(buf1);
8966         }
8967       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8968       return;
8969     }
8970     if (!strncmp(message, "tellall ", 8)) {
8971       if (appData.icsActive) {
8972         if (loggedOn) {
8973           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8974           SendToICS(buf1);
8975         }
8976       } else {
8977         DisplayNote(message + 8);
8978       }
8979       return;
8980     }
8981     if (strncmp(message, "warning", 7) == 0) {
8982         /* Undocumented feature, use tellusererror in new code */
8983         DisplayError(message, 0);
8984         return;
8985     }
8986     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8987         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8988         strcat(realname, " query");
8989         AskQuestion(realname, buf2, buf1, cps->pr);
8990         return;
8991     }
8992     /* Commands from the engine directly to ICS.  We don't allow these to be
8993      *  sent until we are logged on. Crafty kibitzes have been known to
8994      *  interfere with the login process.
8995      */
8996     if (loggedOn) {
8997         if (!strncmp(message, "tellics ", 8)) {
8998             SendToICS(message + 8);
8999             SendToICS("\n");
9000             return;
9001         }
9002         if (!strncmp(message, "tellicsnoalias ", 15)) {
9003             SendToICS(ics_prefix);
9004             SendToICS(message + 15);
9005             SendToICS("\n");
9006             return;
9007         }
9008         /* The following are for backward compatibility only */
9009         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9010             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9011             SendToICS(ics_prefix);
9012             SendToICS(message);
9013             SendToICS("\n");
9014             return;
9015         }
9016     }
9017     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9018         if(initPing == cps->lastPong) {
9019             if(gameInfo.variant == VariantUnknown) {
9020                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9021                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9022                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9023             }
9024             initPing = -1;
9025         }
9026         return;
9027     }
9028     if(!strncmp(message, "highlight ", 10)) {
9029         if(appData.testLegality && appData.markers) return;
9030         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9031         return;
9032     }
9033     if(!strncmp(message, "click ", 6)) {
9034         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9035         if(appData.testLegality || !appData.oneClick) return;
9036         sscanf(message+6, "%c%d%c", &f, &y, &c);
9037         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9038         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9039         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9040         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9041         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9042         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9043             LeftClick(Release, lastLeftX, lastLeftY);
9044         controlKey  = (c == ',');
9045         LeftClick(Press, x, y);
9046         LeftClick(Release, x, y);
9047         first.highlight = f;
9048         return;
9049     }
9050     /*
9051      * If the move is illegal, cancel it and redraw the board.
9052      * Also deal with other error cases.  Matching is rather loose
9053      * here to accommodate engines written before the spec.
9054      */
9055     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9056         strncmp(message, "Error", 5) == 0) {
9057         if (StrStr(message, "name") ||
9058             StrStr(message, "rating") || StrStr(message, "?") ||
9059             StrStr(message, "result") || StrStr(message, "board") ||
9060             StrStr(message, "bk") || StrStr(message, "computer") ||
9061             StrStr(message, "variant") || StrStr(message, "hint") ||
9062             StrStr(message, "random") || StrStr(message, "depth") ||
9063             StrStr(message, "accepted")) {
9064             return;
9065         }
9066         if (StrStr(message, "protover")) {
9067           /* Program is responding to input, so it's apparently done
9068              initializing, and this error message indicates it is
9069              protocol version 1.  So we don't need to wait any longer
9070              for it to initialize and send feature commands. */
9071           FeatureDone(cps, 1);
9072           cps->protocolVersion = 1;
9073           return;
9074         }
9075         cps->maybeThinking = FALSE;
9076
9077         if (StrStr(message, "draw")) {
9078             /* Program doesn't have "draw" command */
9079             cps->sendDrawOffers = 0;
9080             return;
9081         }
9082         if (cps->sendTime != 1 &&
9083             (StrStr(message, "time") || StrStr(message, "otim"))) {
9084           /* Program apparently doesn't have "time" or "otim" command */
9085           cps->sendTime = 0;
9086           return;
9087         }
9088         if (StrStr(message, "analyze")) {
9089             cps->analysisSupport = FALSE;
9090             cps->analyzing = FALSE;
9091 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9092             EditGameEvent(); // [HGM] try to preserve loaded game
9093             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9094             DisplayError(buf2, 0);
9095             return;
9096         }
9097         if (StrStr(message, "(no matching move)st")) {
9098           /* Special kludge for GNU Chess 4 only */
9099           cps->stKludge = TRUE;
9100           SendTimeControl(cps, movesPerSession, timeControl,
9101                           timeIncrement, appData.searchDepth,
9102                           searchTime);
9103           return;
9104         }
9105         if (StrStr(message, "(no matching move)sd")) {
9106           /* Special kludge for GNU Chess 4 only */
9107           cps->sdKludge = TRUE;
9108           SendTimeControl(cps, movesPerSession, timeControl,
9109                           timeIncrement, appData.searchDepth,
9110                           searchTime);
9111           return;
9112         }
9113         if (!StrStr(message, "llegal")) {
9114             return;
9115         }
9116         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9117             gameMode == IcsIdle) return;
9118         if (forwardMostMove <= backwardMostMove) return;
9119         if (pausing) PauseEvent();
9120       if(appData.forceIllegal) {
9121             // [HGM] illegal: machine refused move; force position after move into it
9122           SendToProgram("force\n", cps);
9123           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9124                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9125                 // when black is to move, while there might be nothing on a2 or black
9126                 // might already have the move. So send the board as if white has the move.
9127                 // But first we must change the stm of the engine, as it refused the last move
9128                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9129                 if(WhiteOnMove(forwardMostMove)) {
9130                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9131                     SendBoard(cps, forwardMostMove); // kludgeless board
9132                 } else {
9133                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9134                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9135                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9136                 }
9137           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9138             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9139                  gameMode == TwoMachinesPlay)
9140               SendToProgram("go\n", cps);
9141             return;
9142       } else
9143         if (gameMode == PlayFromGameFile) {
9144             /* Stop reading this game file */
9145             gameMode = EditGame;
9146             ModeHighlight();
9147         }
9148         /* [HGM] illegal-move claim should forfeit game when Xboard */
9149         /* only passes fully legal moves                            */
9150         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9151             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9152                                 "False illegal-move claim", GE_XBOARD );
9153             return; // do not take back move we tested as valid
9154         }
9155         currentMove = forwardMostMove-1;
9156         DisplayMove(currentMove-1); /* before DisplayMoveError */
9157         SwitchClocks(forwardMostMove-1); // [HGM] race
9158         DisplayBothClocks();
9159         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9160                 parseList[currentMove], _(cps->which));
9161         DisplayMoveError(buf1);
9162         DrawPosition(FALSE, boards[currentMove]);
9163
9164         SetUserThinkingEnables();
9165         return;
9166     }
9167     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9168         /* Program has a broken "time" command that
9169            outputs a string not ending in newline.
9170            Don't use it. */
9171         cps->sendTime = 0;
9172     }
9173     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9174         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9175             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9176     }
9177
9178     /*
9179      * If chess program startup fails, exit with an error message.
9180      * Attempts to recover here are futile. [HGM] Well, we try anyway
9181      */
9182     if ((StrStr(message, "unknown host") != NULL)
9183         || (StrStr(message, "No remote directory") != NULL)
9184         || (StrStr(message, "not found") != NULL)
9185         || (StrStr(message, "No such file") != NULL)
9186         || (StrStr(message, "can't alloc") != NULL)
9187         || (StrStr(message, "Permission denied") != NULL)) {
9188
9189         cps->maybeThinking = FALSE;
9190         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9191                 _(cps->which), cps->program, cps->host, message);
9192         RemoveInputSource(cps->isr);
9193         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9194             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9195             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9196         }
9197         return;
9198     }
9199
9200     /*
9201      * Look for hint output
9202      */
9203     if (sscanf(message, "Hint: %s", buf1) == 1) {
9204         if (cps == &first && hintRequested) {
9205             hintRequested = FALSE;
9206             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9207                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9208                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9209                                     PosFlags(forwardMostMove),
9210                                     fromY, fromX, toY, toX, promoChar, buf1);
9211                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9212                 DisplayInformation(buf2);
9213             } else {
9214                 /* Hint move could not be parsed!? */
9215               snprintf(buf2, sizeof(buf2),
9216                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9217                         buf1, _(cps->which));
9218                 DisplayError(buf2, 0);
9219             }
9220         } else {
9221           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9222         }
9223         return;
9224     }
9225
9226     /*
9227      * Ignore other messages if game is not in progress
9228      */
9229     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9230         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9231
9232     /*
9233      * look for win, lose, draw, or draw offer
9234      */
9235     if (strncmp(message, "1-0", 3) == 0) {
9236         char *p, *q, *r = "";
9237         p = strchr(message, '{');
9238         if (p) {
9239             q = strchr(p, '}');
9240             if (q) {
9241                 *q = NULLCHAR;
9242                 r = p + 1;
9243             }
9244         }
9245         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9246         return;
9247     } else if (strncmp(message, "0-1", 3) == 0) {
9248         char *p, *q, *r = "";
9249         p = strchr(message, '{');
9250         if (p) {
9251             q = strchr(p, '}');
9252             if (q) {
9253                 *q = NULLCHAR;
9254                 r = p + 1;
9255             }
9256         }
9257         /* Kludge for Arasan 4.1 bug */
9258         if (strcmp(r, "Black resigns") == 0) {
9259             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9260             return;
9261         }
9262         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9263         return;
9264     } else if (strncmp(message, "1/2", 3) == 0) {
9265         char *p, *q, *r = "";
9266         p = strchr(message, '{');
9267         if (p) {
9268             q = strchr(p, '}');
9269             if (q) {
9270                 *q = NULLCHAR;
9271                 r = p + 1;
9272             }
9273         }
9274
9275         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9276         return;
9277
9278     } else if (strncmp(message, "White resign", 12) == 0) {
9279         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9280         return;
9281     } else if (strncmp(message, "Black resign", 12) == 0) {
9282         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9283         return;
9284     } else if (strncmp(message, "White matches", 13) == 0 ||
9285                strncmp(message, "Black matches", 13) == 0   ) {
9286         /* [HGM] ignore GNUShogi noises */
9287         return;
9288     } else if (strncmp(message, "White", 5) == 0 &&
9289                message[5] != '(' &&
9290                StrStr(message, "Black") == NULL) {
9291         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9292         return;
9293     } else if (strncmp(message, "Black", 5) == 0 &&
9294                message[5] != '(') {
9295         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9296         return;
9297     } else if (strcmp(message, "resign") == 0 ||
9298                strcmp(message, "computer resigns") == 0) {
9299         switch (gameMode) {
9300           case MachinePlaysBlack:
9301           case IcsPlayingBlack:
9302             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9303             break;
9304           case MachinePlaysWhite:
9305           case IcsPlayingWhite:
9306             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9307             break;
9308           case TwoMachinesPlay:
9309             if (cps->twoMachinesColor[0] == 'w')
9310               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9311             else
9312               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9313             break;
9314           default:
9315             /* can't happen */
9316             break;
9317         }
9318         return;
9319     } else if (strncmp(message, "opponent mates", 14) == 0) {
9320         switch (gameMode) {
9321           case MachinePlaysBlack:
9322           case IcsPlayingBlack:
9323             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9324             break;
9325           case MachinePlaysWhite:
9326           case IcsPlayingWhite:
9327             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9328             break;
9329           case TwoMachinesPlay:
9330             if (cps->twoMachinesColor[0] == 'w')
9331               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9332             else
9333               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9334             break;
9335           default:
9336             /* can't happen */
9337             break;
9338         }
9339         return;
9340     } else if (strncmp(message, "computer mates", 14) == 0) {
9341         switch (gameMode) {
9342           case MachinePlaysBlack:
9343           case IcsPlayingBlack:
9344             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9345             break;
9346           case MachinePlaysWhite:
9347           case IcsPlayingWhite:
9348             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9349             break;
9350           case TwoMachinesPlay:
9351             if (cps->twoMachinesColor[0] == 'w')
9352               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9353             else
9354               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9355             break;
9356           default:
9357             /* can't happen */
9358             break;
9359         }
9360         return;
9361     } else if (strncmp(message, "checkmate", 9) == 0) {
9362         if (WhiteOnMove(forwardMostMove)) {
9363             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9364         } else {
9365             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9366         }
9367         return;
9368     } else if (strstr(message, "Draw") != NULL ||
9369                strstr(message, "game is a draw") != NULL) {
9370         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9371         return;
9372     } else if (strstr(message, "offer") != NULL &&
9373                strstr(message, "draw") != NULL) {
9374 #if ZIPPY
9375         if (appData.zippyPlay && first.initDone) {
9376             /* Relay offer to ICS */
9377             SendToICS(ics_prefix);
9378             SendToICS("draw\n");
9379         }
9380 #endif
9381         cps->offeredDraw = 2; /* valid until this engine moves twice */
9382         if (gameMode == TwoMachinesPlay) {
9383             if (cps->other->offeredDraw) {
9384                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9385             /* [HGM] in two-machine mode we delay relaying draw offer      */
9386             /* until after we also have move, to see if it is really claim */
9387             }
9388         } else if (gameMode == MachinePlaysWhite ||
9389                    gameMode == MachinePlaysBlack) {
9390           if (userOfferedDraw) {
9391             DisplayInformation(_("Machine accepts your draw offer"));
9392             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9393           } else {
9394             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9395           }
9396         }
9397     }
9398
9399
9400     /*
9401      * Look for thinking output
9402      */
9403     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9404           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9405                                 ) {
9406         int plylev, mvleft, mvtot, curscore, time;
9407         char mvname[MOVE_LEN];
9408         u64 nodes; // [DM]
9409         char plyext;
9410         int ignore = FALSE;
9411         int prefixHint = FALSE;
9412         mvname[0] = NULLCHAR;
9413
9414         switch (gameMode) {
9415           case MachinePlaysBlack:
9416           case IcsPlayingBlack:
9417             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9418             break;
9419           case MachinePlaysWhite:
9420           case IcsPlayingWhite:
9421             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9422             break;
9423           case AnalyzeMode:
9424           case AnalyzeFile:
9425             break;
9426           case IcsObserving: /* [DM] icsEngineAnalyze */
9427             if (!appData.icsEngineAnalyze) ignore = TRUE;
9428             break;
9429           case TwoMachinesPlay:
9430             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9431                 ignore = TRUE;
9432             }
9433             break;
9434           default:
9435             ignore = TRUE;
9436             break;
9437         }
9438
9439         if (!ignore) {
9440             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9441             buf1[0] = NULLCHAR;
9442             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9443                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9444
9445                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9446                     nodes += u64Const(0x100000000);
9447
9448                 if (plyext != ' ' && plyext != '\t') {
9449                     time *= 100;
9450                 }
9451
9452                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9453                 if( cps->scoreIsAbsolute &&
9454                     ( gameMode == MachinePlaysBlack ||
9455                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9456                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9457                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9458                      !WhiteOnMove(currentMove)
9459                     ) )
9460                 {
9461                     curscore = -curscore;
9462                 }
9463
9464                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9465
9466                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9467                         char buf[MSG_SIZ];
9468                         FILE *f;
9469                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9470                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9471                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9472                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9473                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9474                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9475                                 fclose(f);
9476                         }
9477                         else
9478                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9479                           DisplayError(_("failed writing PV"), 0);
9480                 }
9481
9482                 tempStats.depth = plylev;
9483                 tempStats.nodes = nodes;
9484                 tempStats.time = time;
9485                 tempStats.score = curscore;
9486                 tempStats.got_only_move = 0;
9487
9488                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9489                         int ticklen;
9490
9491                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9492                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9493                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9494                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9495                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9496                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9497                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9498                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9499                 }
9500
9501                 /* Buffer overflow protection */
9502                 if (pv[0] != NULLCHAR) {
9503                     if (strlen(pv) >= sizeof(tempStats.movelist)
9504                         && appData.debugMode) {
9505                         fprintf(debugFP,
9506                                 "PV is too long; using the first %u bytes.\n",
9507                                 (unsigned) sizeof(tempStats.movelist) - 1);
9508                     }
9509
9510                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9511                 } else {
9512                     sprintf(tempStats.movelist, " no PV\n");
9513                 }
9514
9515                 if (tempStats.seen_stat) {
9516                     tempStats.ok_to_send = 1;
9517                 }
9518
9519                 if (strchr(tempStats.movelist, '(') != NULL) {
9520                     tempStats.line_is_book = 1;
9521                     tempStats.nr_moves = 0;
9522                     tempStats.moves_left = 0;
9523                 } else {
9524                     tempStats.line_is_book = 0;
9525                 }
9526
9527                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9528                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9529
9530                 SendProgramStatsToFrontend( cps, &tempStats );
9531
9532                 /*
9533                     [AS] Protect the thinkOutput buffer from overflow... this
9534                     is only useful if buf1 hasn't overflowed first!
9535                 */
9536                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9537                          plylev,
9538                          (gameMode == TwoMachinesPlay ?
9539                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9540                          ((double) curscore) / 100.0,
9541                          prefixHint ? lastHint : "",
9542                          prefixHint ? " " : "" );
9543
9544                 if( buf1[0] != NULLCHAR ) {
9545                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9546
9547                     if( strlen(pv) > max_len ) {
9548                         if( appData.debugMode) {
9549                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9550                         }
9551                         pv[max_len+1] = '\0';
9552                     }
9553
9554                     strcat( thinkOutput, pv);
9555                 }
9556
9557                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9558                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9559                     DisplayMove(currentMove - 1);
9560                 }
9561                 return;
9562
9563             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9564                 /* crafty (9.25+) says "(only move) <move>"
9565                  * if there is only 1 legal move
9566                  */
9567                 sscanf(p, "(only move) %s", buf1);
9568                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9569                 sprintf(programStats.movelist, "%s (only move)", buf1);
9570                 programStats.depth = 1;
9571                 programStats.nr_moves = 1;
9572                 programStats.moves_left = 1;
9573                 programStats.nodes = 1;
9574                 programStats.time = 1;
9575                 programStats.got_only_move = 1;
9576
9577                 /* Not really, but we also use this member to
9578                    mean "line isn't going to change" (Crafty
9579                    isn't searching, so stats won't change) */
9580                 programStats.line_is_book = 1;
9581
9582                 SendProgramStatsToFrontend( cps, &programStats );
9583
9584                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9585                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9586                     DisplayMove(currentMove - 1);
9587                 }
9588                 return;
9589             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9590                               &time, &nodes, &plylev, &mvleft,
9591                               &mvtot, mvname) >= 5) {
9592                 /* The stat01: line is from Crafty (9.29+) in response
9593                    to the "." command */
9594                 programStats.seen_stat = 1;
9595                 cps->maybeThinking = TRUE;
9596
9597                 if (programStats.got_only_move || !appData.periodicUpdates)
9598                   return;
9599
9600                 programStats.depth = plylev;
9601                 programStats.time = time;
9602                 programStats.nodes = nodes;
9603                 programStats.moves_left = mvleft;
9604                 programStats.nr_moves = mvtot;
9605                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9606                 programStats.ok_to_send = 1;
9607                 programStats.movelist[0] = '\0';
9608
9609                 SendProgramStatsToFrontend( cps, &programStats );
9610
9611                 return;
9612
9613             } else if (strncmp(message,"++",2) == 0) {
9614                 /* Crafty 9.29+ outputs this */
9615                 programStats.got_fail = 2;
9616                 return;
9617
9618             } else if (strncmp(message,"--",2) == 0) {
9619                 /* Crafty 9.29+ outputs this */
9620                 programStats.got_fail = 1;
9621                 return;
9622
9623             } else if (thinkOutput[0] != NULLCHAR &&
9624                        strncmp(message, "    ", 4) == 0) {
9625                 unsigned message_len;
9626
9627                 p = message;
9628                 while (*p && *p == ' ') p++;
9629
9630                 message_len = strlen( p );
9631
9632                 /* [AS] Avoid buffer overflow */
9633                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9634                     strcat(thinkOutput, " ");
9635                     strcat(thinkOutput, p);
9636                 }
9637
9638                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9639                     strcat(programStats.movelist, " ");
9640                     strcat(programStats.movelist, p);
9641                 }
9642
9643                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9644                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9645                     DisplayMove(currentMove - 1);
9646                 }
9647                 return;
9648             }
9649         }
9650         else {
9651             buf1[0] = NULLCHAR;
9652
9653             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9654                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9655             {
9656                 ChessProgramStats cpstats;
9657
9658                 if (plyext != ' ' && plyext != '\t') {
9659                     time *= 100;
9660                 }
9661
9662                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9663                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9664                     curscore = -curscore;
9665                 }
9666
9667                 cpstats.depth = plylev;
9668                 cpstats.nodes = nodes;
9669                 cpstats.time = time;
9670                 cpstats.score = curscore;
9671                 cpstats.got_only_move = 0;
9672                 cpstats.movelist[0] = '\0';
9673
9674                 if (buf1[0] != NULLCHAR) {
9675                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9676                 }
9677
9678                 cpstats.ok_to_send = 0;
9679                 cpstats.line_is_book = 0;
9680                 cpstats.nr_moves = 0;
9681                 cpstats.moves_left = 0;
9682
9683                 SendProgramStatsToFrontend( cps, &cpstats );
9684             }
9685         }
9686     }
9687 }
9688
9689
9690 /* Parse a game score from the character string "game", and
9691    record it as the history of the current game.  The game
9692    score is NOT assumed to start from the standard position.
9693    The display is not updated in any way.
9694    */
9695 void
9696 ParseGameHistory (char *game)
9697 {
9698     ChessMove moveType;
9699     int fromX, fromY, toX, toY, boardIndex;
9700     char promoChar;
9701     char *p, *q;
9702     char buf[MSG_SIZ];
9703
9704     if (appData.debugMode)
9705       fprintf(debugFP, "Parsing game history: %s\n", game);
9706
9707     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9708     gameInfo.site = StrSave(appData.icsHost);
9709     gameInfo.date = PGNDate();
9710     gameInfo.round = StrSave("-");
9711
9712     /* Parse out names of players */
9713     while (*game == ' ') game++;
9714     p = buf;
9715     while (*game != ' ') *p++ = *game++;
9716     *p = NULLCHAR;
9717     gameInfo.white = StrSave(buf);
9718     while (*game == ' ') game++;
9719     p = buf;
9720     while (*game != ' ' && *game != '\n') *p++ = *game++;
9721     *p = NULLCHAR;
9722     gameInfo.black = StrSave(buf);
9723
9724     /* Parse moves */
9725     boardIndex = blackPlaysFirst ? 1 : 0;
9726     yynewstr(game);
9727     for (;;) {
9728         yyboardindex = boardIndex;
9729         moveType = (ChessMove) Myylex();
9730         switch (moveType) {
9731           case IllegalMove:             /* maybe suicide chess, etc. */
9732   if (appData.debugMode) {
9733     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9734     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9735     setbuf(debugFP, NULL);
9736   }
9737           case WhitePromotion:
9738           case BlackPromotion:
9739           case WhiteNonPromotion:
9740           case BlackNonPromotion:
9741           case NormalMove:
9742           case FirstLeg:
9743           case WhiteCapturesEnPassant:
9744           case BlackCapturesEnPassant:
9745           case WhiteKingSideCastle:
9746           case WhiteQueenSideCastle:
9747           case BlackKingSideCastle:
9748           case BlackQueenSideCastle:
9749           case WhiteKingSideCastleWild:
9750           case WhiteQueenSideCastleWild:
9751           case BlackKingSideCastleWild:
9752           case BlackQueenSideCastleWild:
9753           /* PUSH Fabien */
9754           case WhiteHSideCastleFR:
9755           case WhiteASideCastleFR:
9756           case BlackHSideCastleFR:
9757           case BlackASideCastleFR:
9758           /* POP Fabien */
9759             fromX = currentMoveString[0] - AAA;
9760             fromY = currentMoveString[1] - ONE;
9761             toX = currentMoveString[2] - AAA;
9762             toY = currentMoveString[3] - ONE;
9763             promoChar = currentMoveString[4];
9764             break;
9765           case WhiteDrop:
9766           case BlackDrop:
9767             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9768             fromX = moveType == WhiteDrop ?
9769               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9770             (int) CharToPiece(ToLower(currentMoveString[0]));
9771             fromY = DROP_RANK;
9772             toX = currentMoveString[2] - AAA;
9773             toY = currentMoveString[3] - ONE;
9774             promoChar = NULLCHAR;
9775             break;
9776           case AmbiguousMove:
9777             /* bug? */
9778             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9779   if (appData.debugMode) {
9780     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9781     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9782     setbuf(debugFP, NULL);
9783   }
9784             DisplayError(buf, 0);
9785             return;
9786           case ImpossibleMove:
9787             /* bug? */
9788             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9789   if (appData.debugMode) {
9790     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9791     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9792     setbuf(debugFP, NULL);
9793   }
9794             DisplayError(buf, 0);
9795             return;
9796           case EndOfFile:
9797             if (boardIndex < backwardMostMove) {
9798                 /* Oops, gap.  How did that happen? */
9799                 DisplayError(_("Gap in move list"), 0);
9800                 return;
9801             }
9802             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9803             if (boardIndex > forwardMostMove) {
9804                 forwardMostMove = boardIndex;
9805             }
9806             return;
9807           case ElapsedTime:
9808             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9809                 strcat(parseList[boardIndex-1], " ");
9810                 strcat(parseList[boardIndex-1], yy_text);
9811             }
9812             continue;
9813           case Comment:
9814           case PGNTag:
9815           case NAG:
9816           default:
9817             /* ignore */
9818             continue;
9819           case WhiteWins:
9820           case BlackWins:
9821           case GameIsDrawn:
9822           case GameUnfinished:
9823             if (gameMode == IcsExamining) {
9824                 if (boardIndex < backwardMostMove) {
9825                     /* Oops, gap.  How did that happen? */
9826                     return;
9827                 }
9828                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9829                 return;
9830             }
9831             gameInfo.result = moveType;
9832             p = strchr(yy_text, '{');
9833             if (p == NULL) p = strchr(yy_text, '(');
9834             if (p == NULL) {
9835                 p = yy_text;
9836                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9837             } else {
9838                 q = strchr(p, *p == '{' ? '}' : ')');
9839                 if (q != NULL) *q = NULLCHAR;
9840                 p++;
9841             }
9842             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9843             gameInfo.resultDetails = StrSave(p);
9844             continue;
9845         }
9846         if (boardIndex >= forwardMostMove &&
9847             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9848             backwardMostMove = blackPlaysFirst ? 1 : 0;
9849             return;
9850         }
9851         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9852                                  fromY, fromX, toY, toX, promoChar,
9853                                  parseList[boardIndex]);
9854         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9855         /* currentMoveString is set as a side-effect of yylex */
9856         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9857         strcat(moveList[boardIndex], "\n");
9858         boardIndex++;
9859         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9860         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9861           case MT_NONE:
9862           case MT_STALEMATE:
9863           default:
9864             break;
9865           case MT_CHECK:
9866             if(!IS_SHOGI(gameInfo.variant))
9867                 strcat(parseList[boardIndex - 1], "+");
9868             break;
9869           case MT_CHECKMATE:
9870           case MT_STAINMATE:
9871             strcat(parseList[boardIndex - 1], "#");
9872             break;
9873         }
9874     }
9875 }
9876
9877
9878 /* Apply a move to the given board  */
9879 void
9880 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9881 {
9882   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9883   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9884
9885     /* [HGM] compute & store e.p. status and castling rights for new position */
9886     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9887
9888       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9889       oldEP = (signed char)board[EP_STATUS];
9890       board[EP_STATUS] = EP_NONE;
9891       board[EP_FILE] = board[EP_RANK] = 100;
9892
9893   if (fromY == DROP_RANK) {
9894         /* must be first */
9895         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9896             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9897             return;
9898         }
9899         piece = board[toY][toX] = (ChessSquare) fromX;
9900   } else {
9901 //      ChessSquare victim;
9902       int i;
9903
9904       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9905 //           victim = board[killY][killX],
9906            board[killY][killX] = EmptySquare,
9907            board[EP_STATUS] = EP_CAPTURE;
9908
9909       if( board[toY][toX] != EmptySquare ) {
9910            board[EP_STATUS] = EP_CAPTURE;
9911            if( (fromX != toX || fromY != toY) && // not igui!
9912                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9913                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9914                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9915            }
9916       }
9917
9918       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9919            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9920                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9921       } else
9922       if( board[fromY][fromX] == WhitePawn ) {
9923            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9924                board[EP_STATUS] = EP_PAWN_MOVE;
9925            if( toY-fromY==2) {
9926                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9927                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9928                         gameInfo.variant != VariantBerolina || toX < fromX)
9929                       board[EP_STATUS] = toX | berolina;
9930                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9931                         gameInfo.variant != VariantBerolina || toX > fromX)
9932                       board[EP_STATUS] = toX;
9933            }
9934       } else
9935       if( board[fromY][fromX] == BlackPawn ) {
9936            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9937                board[EP_STATUS] = EP_PAWN_MOVE;
9938            if( toY-fromY== -2) {
9939                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9940                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9941                         gameInfo.variant != VariantBerolina || toX < fromX)
9942                       board[EP_STATUS] = toX | berolina;
9943                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9944                         gameInfo.variant != VariantBerolina || toX > fromX)
9945                       board[EP_STATUS] = toX;
9946            }
9947        }
9948
9949        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9950        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9951        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9952        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9953
9954        for(i=0; i<nrCastlingRights; i++) {
9955            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9956               board[CASTLING][i] == toX   && castlingRank[i] == toY
9957              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9958        }
9959
9960        if(gameInfo.variant == VariantSChess) { // update virginity
9961            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9962            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9963            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9964            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9965        }
9966
9967      if (fromX == toX && fromY == toY) return;
9968
9969      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9970      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9971      if(gameInfo.variant == VariantKnightmate)
9972          king += (int) WhiteUnicorn - (int) WhiteKing;
9973
9974     /* Code added by Tord: */
9975     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9976     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9977         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9978       board[EP_STATUS] = EP_NONE; // capture was fake!
9979       board[fromY][fromX] = EmptySquare;
9980       board[toY][toX] = EmptySquare;
9981       if((toX > fromX) != (piece == WhiteRook)) {
9982         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9983       } else {
9984         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9985       }
9986     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9987                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9988       board[EP_STATUS] = EP_NONE;
9989       board[fromY][fromX] = EmptySquare;
9990       board[toY][toX] = EmptySquare;
9991       if((toX > fromX) != (piece == BlackRook)) {
9992         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9993       } else {
9994         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9995       }
9996     /* End of code added by Tord */
9997
9998     } else if (board[fromY][fromX] == king
9999         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10000         && toY == fromY && toX > fromX+1) {
10001         board[fromY][fromX] = EmptySquare;
10002         board[toY][toX] = king;
10003         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10004         board[fromY][BOARD_RGHT-1] = EmptySquare;
10005     } else if (board[fromY][fromX] == king
10006         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10007                && toY == fromY && toX < fromX-1) {
10008         board[fromY][fromX] = EmptySquare;
10009         board[toY][toX] = king;
10010         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10011         board[fromY][BOARD_LEFT] = EmptySquare;
10012     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10013                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10014                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10015                ) {
10016         /* white pawn promotion */
10017         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10018         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10019             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10020         board[fromY][fromX] = EmptySquare;
10021     } else if ((fromY >= BOARD_HEIGHT>>1)
10022                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10023                && (toX != fromX)
10024                && gameInfo.variant != VariantXiangqi
10025                && gameInfo.variant != VariantBerolina
10026                && (board[fromY][fromX] == WhitePawn)
10027                && (board[toY][toX] == EmptySquare)) {
10028         board[fromY][fromX] = EmptySquare;
10029         board[toY][toX] = WhitePawn;
10030         captured = board[toY - 1][toX];
10031         board[toY - 1][toX] = EmptySquare;
10032     } else if ((fromY == BOARD_HEIGHT-4)
10033                && (toX == fromX)
10034                && gameInfo.variant == VariantBerolina
10035                && (board[fromY][fromX] == WhitePawn)
10036                && (board[toY][toX] == EmptySquare)) {
10037         board[fromY][fromX] = EmptySquare;
10038         board[toY][toX] = WhitePawn;
10039         if(oldEP & EP_BEROLIN_A) {
10040                 captured = board[fromY][fromX-1];
10041                 board[fromY][fromX-1] = EmptySquare;
10042         }else{  captured = board[fromY][fromX+1];
10043                 board[fromY][fromX+1] = EmptySquare;
10044         }
10045     } else if (board[fromY][fromX] == king
10046         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10047                && toY == fromY && toX > fromX+1) {
10048         board[fromY][fromX] = EmptySquare;
10049         board[toY][toX] = king;
10050         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10051         board[fromY][BOARD_RGHT-1] = EmptySquare;
10052     } else if (board[fromY][fromX] == king
10053         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10054                && toY == fromY && toX < fromX-1) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = king;
10057         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10058         board[fromY][BOARD_LEFT] = EmptySquare;
10059     } else if (fromY == 7 && fromX == 3
10060                && board[fromY][fromX] == BlackKing
10061                && toY == 7 && toX == 5) {
10062         board[fromY][fromX] = EmptySquare;
10063         board[toY][toX] = BlackKing;
10064         board[fromY][7] = EmptySquare;
10065         board[toY][4] = BlackRook;
10066     } else if (fromY == 7 && fromX == 3
10067                && board[fromY][fromX] == BlackKing
10068                && toY == 7 && toX == 1) {
10069         board[fromY][fromX] = EmptySquare;
10070         board[toY][toX] = BlackKing;
10071         board[fromY][0] = EmptySquare;
10072         board[toY][2] = BlackRook;
10073     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10074                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10075                && toY < promoRank && promoChar
10076                ) {
10077         /* black pawn promotion */
10078         board[toY][toX] = CharToPiece(ToLower(promoChar));
10079         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10080             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10081         board[fromY][fromX] = EmptySquare;
10082     } else if ((fromY < BOARD_HEIGHT>>1)
10083                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10084                && (toX != fromX)
10085                && gameInfo.variant != VariantXiangqi
10086                && gameInfo.variant != VariantBerolina
10087                && (board[fromY][fromX] == BlackPawn)
10088                && (board[toY][toX] == EmptySquare)) {
10089         board[fromY][fromX] = EmptySquare;
10090         board[toY][toX] = BlackPawn;
10091         captured = board[toY + 1][toX];
10092         board[toY + 1][toX] = EmptySquare;
10093     } else if ((fromY == 3)
10094                && (toX == fromX)
10095                && gameInfo.variant == VariantBerolina
10096                && (board[fromY][fromX] == BlackPawn)
10097                && (board[toY][toX] == EmptySquare)) {
10098         board[fromY][fromX] = EmptySquare;
10099         board[toY][toX] = BlackPawn;
10100         if(oldEP & EP_BEROLIN_A) {
10101                 captured = board[fromY][fromX-1];
10102                 board[fromY][fromX-1] = EmptySquare;
10103         }else{  captured = board[fromY][fromX+1];
10104                 board[fromY][fromX+1] = EmptySquare;
10105         }
10106     } else {
10107         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10108         board[fromY][fromX] = EmptySquare;
10109         board[toY][toX] = piece;
10110     }
10111   }
10112
10113     if (gameInfo.holdingsWidth != 0) {
10114
10115       /* !!A lot more code needs to be written to support holdings  */
10116       /* [HGM] OK, so I have written it. Holdings are stored in the */
10117       /* penultimate board files, so they are automaticlly stored   */
10118       /* in the game history.                                       */
10119       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10120                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10121         /* Delete from holdings, by decreasing count */
10122         /* and erasing image if necessary            */
10123         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10124         if(p < (int) BlackPawn) { /* white drop */
10125              p -= (int)WhitePawn;
10126                  p = PieceToNumber((ChessSquare)p);
10127              if(p >= gameInfo.holdingsSize) p = 0;
10128              if(--board[p][BOARD_WIDTH-2] <= 0)
10129                   board[p][BOARD_WIDTH-1] = EmptySquare;
10130              if((int)board[p][BOARD_WIDTH-2] < 0)
10131                         board[p][BOARD_WIDTH-2] = 0;
10132         } else {                  /* black drop */
10133              p -= (int)BlackPawn;
10134                  p = PieceToNumber((ChessSquare)p);
10135              if(p >= gameInfo.holdingsSize) p = 0;
10136              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10137                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10138              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10139                         board[BOARD_HEIGHT-1-p][1] = 0;
10140         }
10141       }
10142       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10143           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10144         /* [HGM] holdings: Add to holdings, if holdings exist */
10145         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10146                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10147                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10148         }
10149         p = (int) captured;
10150         if (p >= (int) BlackPawn) {
10151           p -= (int)BlackPawn;
10152           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10153                   /* Restore shogi-promoted piece to its original  first */
10154                   captured = (ChessSquare) (DEMOTED captured);
10155                   p = DEMOTED p;
10156           }
10157           p = PieceToNumber((ChessSquare)p);
10158           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10159           board[p][BOARD_WIDTH-2]++;
10160           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10161         } else {
10162           p -= (int)WhitePawn;
10163           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10164                   captured = (ChessSquare) (DEMOTED captured);
10165                   p = DEMOTED p;
10166           }
10167           p = PieceToNumber((ChessSquare)p);
10168           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10169           board[BOARD_HEIGHT-1-p][1]++;
10170           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10171         }
10172       }
10173     } else if (gameInfo.variant == VariantAtomic) {
10174       if (captured != EmptySquare) {
10175         int y, x;
10176         for (y = toY-1; y <= toY+1; y++) {
10177           for (x = toX-1; x <= toX+1; x++) {
10178             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10179                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10180               board[y][x] = EmptySquare;
10181             }
10182           }
10183         }
10184         board[toY][toX] = EmptySquare;
10185       }
10186     }
10187
10188     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10189         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10190     } else
10191     if(promoChar == '+') {
10192         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10193         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10194         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10195           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10196     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10197         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10198         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10199            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10200         board[toY][toX] = newPiece;
10201     }
10202     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10203                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10204         // [HGM] superchess: take promotion piece out of holdings
10205         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10206         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10207             if(!--board[k][BOARD_WIDTH-2])
10208                 board[k][BOARD_WIDTH-1] = EmptySquare;
10209         } else {
10210             if(!--board[BOARD_HEIGHT-1-k][1])
10211                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10212         }
10213     }
10214 }
10215
10216 /* Updates forwardMostMove */
10217 void
10218 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10219 {
10220     int x = toX, y = toY;
10221     char *s = parseList[forwardMostMove];
10222     ChessSquare p = boards[forwardMostMove][toY][toX];
10223 //    forwardMostMove++; // [HGM] bare: moved downstream
10224
10225     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10226     (void) CoordsToAlgebraic(boards[forwardMostMove],
10227                              PosFlags(forwardMostMove),
10228                              fromY, fromX, y, x, promoChar,
10229                              s);
10230     if(killX >= 0 && killY >= 0)
10231         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10232
10233     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10234         int timeLeft; static int lastLoadFlag=0; int king, piece;
10235         piece = boards[forwardMostMove][fromY][fromX];
10236         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10237         if(gameInfo.variant == VariantKnightmate)
10238             king += (int) WhiteUnicorn - (int) WhiteKing;
10239         if(forwardMostMove == 0) {
10240             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10241                 fprintf(serverMoves, "%s;", UserName());
10242             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10243                 fprintf(serverMoves, "%s;", second.tidy);
10244             fprintf(serverMoves, "%s;", first.tidy);
10245             if(gameMode == MachinePlaysWhite)
10246                 fprintf(serverMoves, "%s;", UserName());
10247             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10248                 fprintf(serverMoves, "%s;", second.tidy);
10249         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10250         lastLoadFlag = loadFlag;
10251         // print base move
10252         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10253         // print castling suffix
10254         if( toY == fromY && piece == king ) {
10255             if(toX-fromX > 1)
10256                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10257             if(fromX-toX >1)
10258                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10259         }
10260         // e.p. suffix
10261         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10262              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10263              boards[forwardMostMove][toY][toX] == EmptySquare
10264              && fromX != toX && fromY != toY)
10265                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10266         // promotion suffix
10267         if(promoChar != NULLCHAR) {
10268             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10269                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10270                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10271             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10272         }
10273         if(!loadFlag) {
10274                 char buf[MOVE_LEN*2], *p; int len;
10275             fprintf(serverMoves, "/%d/%d",
10276                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10277             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10278             else                      timeLeft = blackTimeRemaining/1000;
10279             fprintf(serverMoves, "/%d", timeLeft);
10280                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10281                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10282                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10283                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10284             fprintf(serverMoves, "/%s", buf);
10285         }
10286         fflush(serverMoves);
10287     }
10288
10289     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10290         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10291       return;
10292     }
10293     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10294     if (commentList[forwardMostMove+1] != NULL) {
10295         free(commentList[forwardMostMove+1]);
10296         commentList[forwardMostMove+1] = NULL;
10297     }
10298     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10299     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10300     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10301     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10302     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10303     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10304     adjustedClock = FALSE;
10305     gameInfo.result = GameUnfinished;
10306     if (gameInfo.resultDetails != NULL) {
10307         free(gameInfo.resultDetails);
10308         gameInfo.resultDetails = NULL;
10309     }
10310     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10311                               moveList[forwardMostMove - 1]);
10312     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10313       case MT_NONE:
10314       case MT_STALEMATE:
10315       default:
10316         break;
10317       case MT_CHECK:
10318         if(!IS_SHOGI(gameInfo.variant))
10319             strcat(parseList[forwardMostMove - 1], "+");
10320         break;
10321       case MT_CHECKMATE:
10322       case MT_STAINMATE:
10323         strcat(parseList[forwardMostMove - 1], "#");
10324         break;
10325     }
10326 }
10327
10328 /* Updates currentMove if not pausing */
10329 void
10330 ShowMove (int fromX, int fromY, int toX, int toY)
10331 {
10332     int instant = (gameMode == PlayFromGameFile) ?
10333         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10334     if(appData.noGUI) return;
10335     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10336         if (!instant) {
10337             if (forwardMostMove == currentMove + 1) {
10338                 AnimateMove(boards[forwardMostMove - 1],
10339                             fromX, fromY, toX, toY);
10340             }
10341         }
10342         currentMove = forwardMostMove;
10343     }
10344
10345     killX = killY = -1; // [HGM] lion: used up
10346
10347     if (instant) return;
10348
10349     DisplayMove(currentMove - 1);
10350     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10351             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10352                 SetHighlights(fromX, fromY, toX, toY);
10353             }
10354     }
10355     DrawPosition(FALSE, boards[currentMove]);
10356     DisplayBothClocks();
10357     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10358 }
10359
10360 void
10361 SendEgtPath (ChessProgramState *cps)
10362 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10363         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10364
10365         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10366
10367         while(*p) {
10368             char c, *q = name+1, *r, *s;
10369
10370             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10371             while(*p && *p != ',') *q++ = *p++;
10372             *q++ = ':'; *q = 0;
10373             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10374                 strcmp(name, ",nalimov:") == 0 ) {
10375                 // take nalimov path from the menu-changeable option first, if it is defined
10376               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10377                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10378             } else
10379             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10380                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10381                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10382                 s = r = StrStr(s, ":") + 1; // beginning of path info
10383                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10384                 c = *r; *r = 0;             // temporarily null-terminate path info
10385                     *--q = 0;               // strip of trailig ':' from name
10386                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10387                 *r = c;
10388                 SendToProgram(buf,cps);     // send egtbpath command for this format
10389             }
10390             if(*p == ',') p++; // read away comma to position for next format name
10391         }
10392 }
10393
10394 static int
10395 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10396 {
10397       int width = 8, height = 8, holdings = 0;             // most common sizes
10398       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10399       // correct the deviations default for each variant
10400       if( v == VariantXiangqi ) width = 9,  height = 10;
10401       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10402       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10403       if( v == VariantCapablanca || v == VariantCapaRandom ||
10404           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10405                                 width = 10;
10406       if( v == VariantCourier ) width = 12;
10407       if( v == VariantSuper )                            holdings = 8;
10408       if( v == VariantGreat )   width = 10,              holdings = 8;
10409       if( v == VariantSChess )                           holdings = 7;
10410       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10411       if( v == VariantChuChess) width = 10, height = 10;
10412       if( v == VariantChu )     width = 12, height = 12;
10413       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10414              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10415              holdingsSize >= 0 && holdingsSize != holdings;
10416 }
10417
10418 char variantError[MSG_SIZ];
10419
10420 char *
10421 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10422 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10423       char *p, *variant = VariantName(v);
10424       static char b[MSG_SIZ];
10425       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10426            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10427                                                holdingsSize, variant); // cook up sized variant name
10428            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10429            if(StrStr(list, b) == NULL) {
10430                // specific sized variant not known, check if general sizing allowed
10431                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10432                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10433                             boardWidth, boardHeight, holdingsSize, engine);
10434                    return NULL;
10435                }
10436                /* [HGM] here we really should compare with the maximum supported board size */
10437            }
10438       } else snprintf(b, MSG_SIZ,"%s", variant);
10439       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10440       p = StrStr(list, b);
10441       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10442       if(p == NULL) {
10443           // occurs not at all in list, or only as sub-string
10444           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10445           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10446               int l = strlen(variantError);
10447               char *q;
10448               while(p != list && p[-1] != ',') p--;
10449               q = strchr(p, ',');
10450               if(q) *q = NULLCHAR;
10451               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10452               if(q) *q= ',';
10453           }
10454           return NULL;
10455       }
10456       return b;
10457 }
10458
10459 void
10460 InitChessProgram (ChessProgramState *cps, int setup)
10461 /* setup needed to setup FRC opening position */
10462 {
10463     char buf[MSG_SIZ], *b;
10464     if (appData.noChessProgram) return;
10465     hintRequested = FALSE;
10466     bookRequested = FALSE;
10467
10468     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10469     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10470     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10471     if(cps->memSize) { /* [HGM] memory */
10472       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10473         SendToProgram(buf, cps);
10474     }
10475     SendEgtPath(cps); /* [HGM] EGT */
10476     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10477       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10478         SendToProgram(buf, cps);
10479     }
10480
10481     setboardSpoiledMachineBlack = FALSE;
10482     SendToProgram(cps->initString, cps);
10483     if (gameInfo.variant != VariantNormal &&
10484         gameInfo.variant != VariantLoadable
10485         /* [HGM] also send variant if board size non-standard */
10486         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10487
10488       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10489                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10490       if (b == NULL) {
10491         VariantClass v;
10492         char c, *q = cps->variants, *p = strchr(q, ',');
10493         if(p) *p = NULLCHAR;
10494         v = StringToVariant(q);
10495         DisplayError(variantError, 0);
10496         if(v != VariantUnknown && cps == &first) {
10497             int w, h, s;
10498             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10499                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10500             ASSIGN(appData.variant, q);
10501             Reset(TRUE, FALSE);
10502         }
10503         if(p) *p = ',';
10504         return;
10505       }
10506
10507       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10508       SendToProgram(buf, cps);
10509     }
10510     currentlyInitializedVariant = gameInfo.variant;
10511
10512     /* [HGM] send opening position in FRC to first engine */
10513     if(setup) {
10514           SendToProgram("force\n", cps);
10515           SendBoard(cps, 0);
10516           /* engine is now in force mode! Set flag to wake it up after first move. */
10517           setboardSpoiledMachineBlack = 1;
10518     }
10519
10520     if (cps->sendICS) {
10521       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10522       SendToProgram(buf, cps);
10523     }
10524     cps->maybeThinking = FALSE;
10525     cps->offeredDraw = 0;
10526     if (!appData.icsActive) {
10527         SendTimeControl(cps, movesPerSession, timeControl,
10528                         timeIncrement, appData.searchDepth,
10529                         searchTime);
10530     }
10531     if (appData.showThinking
10532         // [HGM] thinking: four options require thinking output to be sent
10533         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10534                                 ) {
10535         SendToProgram("post\n", cps);
10536     }
10537     SendToProgram("hard\n", cps);
10538     if (!appData.ponderNextMove) {
10539         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10540            it without being sure what state we are in first.  "hard"
10541            is not a toggle, so that one is OK.
10542          */
10543         SendToProgram("easy\n", cps);
10544     }
10545     if (cps->usePing) {
10546       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10547       SendToProgram(buf, cps);
10548     }
10549     cps->initDone = TRUE;
10550     ClearEngineOutputPane(cps == &second);
10551 }
10552
10553
10554 void
10555 ResendOptions (ChessProgramState *cps)
10556 { // send the stored value of the options
10557   int i;
10558   char buf[MSG_SIZ];
10559   Option *opt = cps->option;
10560   for(i=0; i<cps->nrOptions; i++, opt++) {
10561       switch(opt->type) {
10562         case Spin:
10563         case Slider:
10564         case CheckBox:
10565             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10566           break;
10567         case ComboBox:
10568           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10569           break;
10570         default:
10571             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10572           break;
10573         case Button:
10574         case SaveButton:
10575           continue;
10576       }
10577       SendToProgram(buf, cps);
10578   }
10579 }
10580
10581 void
10582 StartChessProgram (ChessProgramState *cps)
10583 {
10584     char buf[MSG_SIZ];
10585     int err;
10586
10587     if (appData.noChessProgram) return;
10588     cps->initDone = FALSE;
10589
10590     if (strcmp(cps->host, "localhost") == 0) {
10591         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10592     } else if (*appData.remoteShell == NULLCHAR) {
10593         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10594     } else {
10595         if (*appData.remoteUser == NULLCHAR) {
10596           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10597                     cps->program);
10598         } else {
10599           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10600                     cps->host, appData.remoteUser, cps->program);
10601         }
10602         err = StartChildProcess(buf, "", &cps->pr);
10603     }
10604
10605     if (err != 0) {
10606       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10607         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10608         if(cps != &first) return;
10609         appData.noChessProgram = TRUE;
10610         ThawUI();
10611         SetNCPMode();
10612 //      DisplayFatalError(buf, err, 1);
10613 //      cps->pr = NoProc;
10614 //      cps->isr = NULL;
10615         return;
10616     }
10617
10618     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10619     if (cps->protocolVersion > 1) {
10620       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10621       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10622         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10623         cps->comboCnt = 0;  //                and values of combo boxes
10624       }
10625       SendToProgram(buf, cps);
10626       if(cps->reload) ResendOptions(cps);
10627     } else {
10628       SendToProgram("xboard\n", cps);
10629     }
10630 }
10631
10632 void
10633 TwoMachinesEventIfReady P((void))
10634 {
10635   static int curMess = 0;
10636   if (first.lastPing != first.lastPong) {
10637     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10638     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10639     return;
10640   }
10641   if (second.lastPing != second.lastPong) {
10642     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10643     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10644     return;
10645   }
10646   DisplayMessage("", ""); curMess = 0;
10647   TwoMachinesEvent();
10648 }
10649
10650 char *
10651 MakeName (char *template)
10652 {
10653     time_t clock;
10654     struct tm *tm;
10655     static char buf[MSG_SIZ];
10656     char *p = buf;
10657     int i;
10658
10659     clock = time((time_t *)NULL);
10660     tm = localtime(&clock);
10661
10662     while(*p++ = *template++) if(p[-1] == '%') {
10663         switch(*template++) {
10664           case 0:   *p = 0; return buf;
10665           case 'Y': i = tm->tm_year+1900; break;
10666           case 'y': i = tm->tm_year-100; break;
10667           case 'M': i = tm->tm_mon+1; break;
10668           case 'd': i = tm->tm_mday; break;
10669           case 'h': i = tm->tm_hour; break;
10670           case 'm': i = tm->tm_min; break;
10671           case 's': i = tm->tm_sec; break;
10672           default:  i = 0;
10673         }
10674         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10675     }
10676     return buf;
10677 }
10678
10679 int
10680 CountPlayers (char *p)
10681 {
10682     int n = 0;
10683     while(p = strchr(p, '\n')) p++, n++; // count participants
10684     return n;
10685 }
10686
10687 FILE *
10688 WriteTourneyFile (char *results, FILE *f)
10689 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10690     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10691     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10692         // create a file with tournament description
10693         fprintf(f, "-participants {%s}\n", appData.participants);
10694         fprintf(f, "-seedBase %d\n", appData.seedBase);
10695         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10696         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10697         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10698         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10699         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10700         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10701         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10702         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10703         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10704         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10705         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10706         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10707         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10708         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10709         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10710         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10711         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10712         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10713         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10714         fprintf(f, "-smpCores %d\n", appData.smpCores);
10715         if(searchTime > 0)
10716                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10717         else {
10718                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10719                 fprintf(f, "-tc %s\n", appData.timeControl);
10720                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10721         }
10722         fprintf(f, "-results \"%s\"\n", results);
10723     }
10724     return f;
10725 }
10726
10727 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10728
10729 void
10730 Substitute (char *participants, int expunge)
10731 {
10732     int i, changed, changes=0, nPlayers=0;
10733     char *p, *q, *r, buf[MSG_SIZ];
10734     if(participants == NULL) return;
10735     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10736     r = p = participants; q = appData.participants;
10737     while(*p && *p == *q) {
10738         if(*p == '\n') r = p+1, nPlayers++;
10739         p++; q++;
10740     }
10741     if(*p) { // difference
10742         while(*p && *p++ != '\n');
10743         while(*q && *q++ != '\n');
10744       changed = nPlayers;
10745         changes = 1 + (strcmp(p, q) != 0);
10746     }
10747     if(changes == 1) { // a single engine mnemonic was changed
10748         q = r; while(*q) nPlayers += (*q++ == '\n');
10749         p = buf; while(*r && (*p = *r++) != '\n') p++;
10750         *p = NULLCHAR;
10751         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10752         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10753         if(mnemonic[i]) { // The substitute is valid
10754             FILE *f;
10755             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10756                 flock(fileno(f), LOCK_EX);
10757                 ParseArgsFromFile(f);
10758                 fseek(f, 0, SEEK_SET);
10759                 FREE(appData.participants); appData.participants = participants;
10760                 if(expunge) { // erase results of replaced engine
10761                     int len = strlen(appData.results), w, b, dummy;
10762                     for(i=0; i<len; i++) {
10763                         Pairing(i, nPlayers, &w, &b, &dummy);
10764                         if((w == changed || b == changed) && appData.results[i] == '*') {
10765                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10766                             fclose(f);
10767                             return;
10768                         }
10769                     }
10770                     for(i=0; i<len; i++) {
10771                         Pairing(i, nPlayers, &w, &b, &dummy);
10772                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10773                     }
10774                 }
10775                 WriteTourneyFile(appData.results, f);
10776                 fclose(f); // release lock
10777                 return;
10778             }
10779         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10780     }
10781     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10782     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10783     free(participants);
10784     return;
10785 }
10786
10787 int
10788 CheckPlayers (char *participants)
10789 {
10790         int i;
10791         char buf[MSG_SIZ], *p;
10792         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10793         while(p = strchr(participants, '\n')) {
10794             *p = NULLCHAR;
10795             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10796             if(!mnemonic[i]) {
10797                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10798                 *p = '\n';
10799                 DisplayError(buf, 0);
10800                 return 1;
10801             }
10802             *p = '\n';
10803             participants = p + 1;
10804         }
10805         return 0;
10806 }
10807
10808 int
10809 CreateTourney (char *name)
10810 {
10811         FILE *f;
10812         if(matchMode && strcmp(name, appData.tourneyFile)) {
10813              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10814         }
10815         if(name[0] == NULLCHAR) {
10816             if(appData.participants[0])
10817                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10818             return 0;
10819         }
10820         f = fopen(name, "r");
10821         if(f) { // file exists
10822             ASSIGN(appData.tourneyFile, name);
10823             ParseArgsFromFile(f); // parse it
10824         } else {
10825             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10826             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10827                 DisplayError(_("Not enough participants"), 0);
10828                 return 0;
10829             }
10830             if(CheckPlayers(appData.participants)) return 0;
10831             ASSIGN(appData.tourneyFile, name);
10832             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10833             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10834         }
10835         fclose(f);
10836         appData.noChessProgram = FALSE;
10837         appData.clockMode = TRUE;
10838         SetGNUMode();
10839         return 1;
10840 }
10841
10842 int
10843 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10844 {
10845     char buf[MSG_SIZ], *p, *q;
10846     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10847     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10848     skip = !all && group[0]; // if group requested, we start in skip mode
10849     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10850         p = names; q = buf; header = 0;
10851         while(*p && *p != '\n') *q++ = *p++;
10852         *q = 0;
10853         if(*p == '\n') p++;
10854         if(buf[0] == '#') {
10855             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10856             depth++; // we must be entering a new group
10857             if(all) continue; // suppress printing group headers when complete list requested
10858             header = 1;
10859             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10860         }
10861         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10862         if(engineList[i]) free(engineList[i]);
10863         engineList[i] = strdup(buf);
10864         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10865         if(engineMnemonic[i]) free(engineMnemonic[i]);
10866         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10867             strcat(buf, " (");
10868             sscanf(q + 8, "%s", buf + strlen(buf));
10869             strcat(buf, ")");
10870         }
10871         engineMnemonic[i] = strdup(buf);
10872         i++;
10873     }
10874     engineList[i] = engineMnemonic[i] = NULL;
10875     return i;
10876 }
10877
10878 // following implemented as macro to avoid type limitations
10879 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10880
10881 void
10882 SwapEngines (int n)
10883 {   // swap settings for first engine and other engine (so far only some selected options)
10884     int h;
10885     char *p;
10886     if(n == 0) return;
10887     SWAP(directory, p)
10888     SWAP(chessProgram, p)
10889     SWAP(isUCI, h)
10890     SWAP(hasOwnBookUCI, h)
10891     SWAP(protocolVersion, h)
10892     SWAP(reuse, h)
10893     SWAP(scoreIsAbsolute, h)
10894     SWAP(timeOdds, h)
10895     SWAP(logo, p)
10896     SWAP(pgnName, p)
10897     SWAP(pvSAN, h)
10898     SWAP(engOptions, p)
10899     SWAP(engInitString, p)
10900     SWAP(computerString, p)
10901     SWAP(features, p)
10902     SWAP(fenOverride, p)
10903     SWAP(NPS, h)
10904     SWAP(accumulateTC, h)
10905     SWAP(drawDepth, h)
10906     SWAP(host, p)
10907     SWAP(pseudo, h)
10908 }
10909
10910 int
10911 GetEngineLine (char *s, int n)
10912 {
10913     int i;
10914     char buf[MSG_SIZ];
10915     extern char *icsNames;
10916     if(!s || !*s) return 0;
10917     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10918     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10919     if(!mnemonic[i]) return 0;
10920     if(n == 11) return 1; // just testing if there was a match
10921     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10922     if(n == 1) SwapEngines(n);
10923     ParseArgsFromString(buf);
10924     if(n == 1) SwapEngines(n);
10925     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10926         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10927         ParseArgsFromString(buf);
10928     }
10929     return 1;
10930 }
10931
10932 int
10933 SetPlayer (int player, char *p)
10934 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10935     int i;
10936     char buf[MSG_SIZ], *engineName;
10937     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10938     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10939     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10940     if(mnemonic[i]) {
10941         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10942         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10943         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10944         ParseArgsFromString(buf);
10945     } else { // no engine with this nickname is installed!
10946         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10947         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10948         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10949         ModeHighlight();
10950         DisplayError(buf, 0);
10951         return 0;
10952     }
10953     free(engineName);
10954     return i;
10955 }
10956
10957 char *recentEngines;
10958
10959 void
10960 RecentEngineEvent (int nr)
10961 {
10962     int n;
10963 //    SwapEngines(1); // bump first to second
10964 //    ReplaceEngine(&second, 1); // and load it there
10965     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10966     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10967     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10968         ReplaceEngine(&first, 0);
10969         FloatToFront(&appData.recentEngineList, command[n]);
10970     }
10971 }
10972
10973 int
10974 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10975 {   // determine players from game number
10976     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10977
10978     if(appData.tourneyType == 0) {
10979         roundsPerCycle = (nPlayers - 1) | 1;
10980         pairingsPerRound = nPlayers / 2;
10981     } else if(appData.tourneyType > 0) {
10982         roundsPerCycle = nPlayers - appData.tourneyType;
10983         pairingsPerRound = appData.tourneyType;
10984     }
10985     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10986     gamesPerCycle = gamesPerRound * roundsPerCycle;
10987     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10988     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10989     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10990     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10991     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10992     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10993
10994     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10995     if(appData.roundSync) *syncInterval = gamesPerRound;
10996
10997     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10998
10999     if(appData.tourneyType == 0) {
11000         if(curPairing == (nPlayers-1)/2 ) {
11001             *whitePlayer = curRound;
11002             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11003         } else {
11004             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11005             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11006             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11007             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11008         }
11009     } else if(appData.tourneyType > 1) {
11010         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11011         *whitePlayer = curRound + appData.tourneyType;
11012     } else if(appData.tourneyType > 0) {
11013         *whitePlayer = curPairing;
11014         *blackPlayer = curRound + appData.tourneyType;
11015     }
11016
11017     // take care of white/black alternation per round.
11018     // For cycles and games this is already taken care of by default, derived from matchGame!
11019     return curRound & 1;
11020 }
11021
11022 int
11023 NextTourneyGame (int nr, int *swapColors)
11024 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11025     char *p, *q;
11026     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11027     FILE *tf;
11028     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11029     tf = fopen(appData.tourneyFile, "r");
11030     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11031     ParseArgsFromFile(tf); fclose(tf);
11032     InitTimeControls(); // TC might be altered from tourney file
11033
11034     nPlayers = CountPlayers(appData.participants); // count participants
11035     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11036     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11037
11038     if(syncInterval) {
11039         p = q = appData.results;
11040         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11041         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11042             DisplayMessage(_("Waiting for other game(s)"),"");
11043             waitingForGame = TRUE;
11044             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11045             return 0;
11046         }
11047         waitingForGame = FALSE;
11048     }
11049
11050     if(appData.tourneyType < 0) {
11051         if(nr>=0 && !pairingReceived) {
11052             char buf[1<<16];
11053             if(pairing.pr == NoProc) {
11054                 if(!appData.pairingEngine[0]) {
11055                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11056                     return 0;
11057                 }
11058                 StartChessProgram(&pairing); // starts the pairing engine
11059             }
11060             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11061             SendToProgram(buf, &pairing);
11062             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11063             SendToProgram(buf, &pairing);
11064             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11065         }
11066         pairingReceived = 0;                              // ... so we continue here
11067         *swapColors = 0;
11068         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11069         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11070         matchGame = 1; roundNr = nr / syncInterval + 1;
11071     }
11072
11073     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11074
11075     // redefine engines, engine dir, etc.
11076     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11077     if(first.pr == NoProc) {
11078       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11079       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11080     }
11081     if(second.pr == NoProc) {
11082       SwapEngines(1);
11083       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11084       SwapEngines(1);         // and make that valid for second engine by swapping
11085       InitEngine(&second, 1);
11086     }
11087     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11088     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11089     return OK;
11090 }
11091
11092 void
11093 NextMatchGame ()
11094 {   // performs game initialization that does not invoke engines, and then tries to start the game
11095     int res, firstWhite, swapColors = 0;
11096     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11097     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
11098         char buf[MSG_SIZ];
11099         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11100         if(strcmp(buf, currentDebugFile)) { // name has changed
11101             FILE *f = fopen(buf, "w");
11102             if(f) { // if opening the new file failed, just keep using the old one
11103                 ASSIGN(currentDebugFile, buf);
11104                 fclose(debugFP);
11105                 debugFP = f;
11106             }
11107             if(appData.serverFileName) {
11108                 if(serverFP) fclose(serverFP);
11109                 serverFP = fopen(appData.serverFileName, "w");
11110                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11111                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11112             }
11113         }
11114     }
11115     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11116     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11117     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11118     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11119     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11120     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11121     Reset(FALSE, first.pr != NoProc);
11122     res = LoadGameOrPosition(matchGame); // setup game
11123     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11124     if(!res) return; // abort when bad game/pos file
11125     TwoMachinesEvent();
11126 }
11127
11128 void
11129 UserAdjudicationEvent (int result)
11130 {
11131     ChessMove gameResult = GameIsDrawn;
11132
11133     if( result > 0 ) {
11134         gameResult = WhiteWins;
11135     }
11136     else if( result < 0 ) {
11137         gameResult = BlackWins;
11138     }
11139
11140     if( gameMode == TwoMachinesPlay ) {
11141         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11142     }
11143 }
11144
11145
11146 // [HGM] save: calculate checksum of game to make games easily identifiable
11147 int
11148 StringCheckSum (char *s)
11149 {
11150         int i = 0;
11151         if(s==NULL) return 0;
11152         while(*s) i = i*259 + *s++;
11153         return i;
11154 }
11155
11156 int
11157 GameCheckSum ()
11158 {
11159         int i, sum=0;
11160         for(i=backwardMostMove; i<forwardMostMove; i++) {
11161                 sum += pvInfoList[i].depth;
11162                 sum += StringCheckSum(parseList[i]);
11163                 sum += StringCheckSum(commentList[i]);
11164                 sum *= 261;
11165         }
11166         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11167         return sum + StringCheckSum(commentList[i]);
11168 } // end of save patch
11169
11170 void
11171 GameEnds (ChessMove result, char *resultDetails, int whosays)
11172 {
11173     GameMode nextGameMode;
11174     int isIcsGame;
11175     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11176
11177     if(endingGame) return; /* [HGM] crash: forbid recursion */
11178     endingGame = 1;
11179     if(twoBoards) { // [HGM] dual: switch back to one board
11180         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11181         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11182     }
11183     if (appData.debugMode) {
11184       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11185               result, resultDetails ? resultDetails : "(null)", whosays);
11186     }
11187
11188     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11189
11190     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11191
11192     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11193         /* If we are playing on ICS, the server decides when the
11194            game is over, but the engine can offer to draw, claim
11195            a draw, or resign.
11196          */
11197 #if ZIPPY
11198         if (appData.zippyPlay && first.initDone) {
11199             if (result == GameIsDrawn) {
11200                 /* In case draw still needs to be claimed */
11201                 SendToICS(ics_prefix);
11202                 SendToICS("draw\n");
11203             } else if (StrCaseStr(resultDetails, "resign")) {
11204                 SendToICS(ics_prefix);
11205                 SendToICS("resign\n");
11206             }
11207         }
11208 #endif
11209         endingGame = 0; /* [HGM] crash */
11210         return;
11211     }
11212
11213     /* If we're loading the game from a file, stop */
11214     if (whosays == GE_FILE) {
11215       (void) StopLoadGameTimer();
11216       gameFileFP = NULL;
11217     }
11218
11219     /* Cancel draw offers */
11220     first.offeredDraw = second.offeredDraw = 0;
11221
11222     /* If this is an ICS game, only ICS can really say it's done;
11223        if not, anyone can. */
11224     isIcsGame = (gameMode == IcsPlayingWhite ||
11225                  gameMode == IcsPlayingBlack ||
11226                  gameMode == IcsObserving    ||
11227                  gameMode == IcsExamining);
11228
11229     if (!isIcsGame || whosays == GE_ICS) {
11230         /* OK -- not an ICS game, or ICS said it was done */
11231         StopClocks();
11232         if (!isIcsGame && !appData.noChessProgram)
11233           SetUserThinkingEnables();
11234
11235         /* [HGM] if a machine claims the game end we verify this claim */
11236         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11237             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11238                 char claimer;
11239                 ChessMove trueResult = (ChessMove) -1;
11240
11241                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11242                                             first.twoMachinesColor[0] :
11243                                             second.twoMachinesColor[0] ;
11244
11245                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11246                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11247                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11248                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11249                 } else
11250                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11251                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11252                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11253                 } else
11254                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11255                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11256                 }
11257
11258                 // now verify win claims, but not in drop games, as we don't understand those yet
11259                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11260                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11261                     (result == WhiteWins && claimer == 'w' ||
11262                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11263                       if (appData.debugMode) {
11264                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11265                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11266                       }
11267                       if(result != trueResult) {
11268                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11269                               result = claimer == 'w' ? BlackWins : WhiteWins;
11270                               resultDetails = buf;
11271                       }
11272                 } else
11273                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11274                     && (forwardMostMove <= backwardMostMove ||
11275                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11276                         (claimer=='b')==(forwardMostMove&1))
11277                                                                                   ) {
11278                       /* [HGM] verify: draws that were not flagged are false claims */
11279                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11280                       result = claimer == 'w' ? BlackWins : WhiteWins;
11281                       resultDetails = buf;
11282                 }
11283                 /* (Claiming a loss is accepted no questions asked!) */
11284             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11285                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11286                 result = GameUnfinished;
11287                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11288             }
11289             /* [HGM] bare: don't allow bare King to win */
11290             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11291                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11292                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11293                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11294                && result != GameIsDrawn)
11295             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11296                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11297                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11298                         if(p >= 0 && p <= (int)WhiteKing) k++;
11299                 }
11300                 if (appData.debugMode) {
11301                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11302                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11303                 }
11304                 if(k <= 1) {
11305                         result = GameIsDrawn;
11306                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11307                         resultDetails = buf;
11308                 }
11309             }
11310         }
11311
11312
11313         if(serverMoves != NULL && !loadFlag) { char c = '=';
11314             if(result==WhiteWins) c = '+';
11315             if(result==BlackWins) c = '-';
11316             if(resultDetails != NULL)
11317                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11318         }
11319         if (resultDetails != NULL) {
11320             gameInfo.result = result;
11321             gameInfo.resultDetails = StrSave(resultDetails);
11322
11323             /* display last move only if game was not loaded from file */
11324             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11325                 DisplayMove(currentMove - 1);
11326
11327             if (forwardMostMove != 0) {
11328                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11329                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11330                                                                 ) {
11331                     if (*appData.saveGameFile != NULLCHAR) {
11332                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11333                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11334                         else
11335                         SaveGameToFile(appData.saveGameFile, TRUE);
11336                     } else if (appData.autoSaveGames) {
11337                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11338                     }
11339                     if (*appData.savePositionFile != NULLCHAR) {
11340                         SavePositionToFile(appData.savePositionFile);
11341                     }
11342                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11343                 }
11344             }
11345
11346             /* Tell program how game ended in case it is learning */
11347             /* [HGM] Moved this to after saving the PGN, just in case */
11348             /* engine died and we got here through time loss. In that */
11349             /* case we will get a fatal error writing the pipe, which */
11350             /* would otherwise lose us the PGN.                       */
11351             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11352             /* output during GameEnds should never be fatal anymore   */
11353             if (gameMode == MachinePlaysWhite ||
11354                 gameMode == MachinePlaysBlack ||
11355                 gameMode == TwoMachinesPlay ||
11356                 gameMode == IcsPlayingWhite ||
11357                 gameMode == IcsPlayingBlack ||
11358                 gameMode == BeginningOfGame) {
11359                 char buf[MSG_SIZ];
11360                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11361                         resultDetails);
11362                 if (first.pr != NoProc) {
11363                     SendToProgram(buf, &first);
11364                 }
11365                 if (second.pr != NoProc &&
11366                     gameMode == TwoMachinesPlay) {
11367                     SendToProgram(buf, &second);
11368                 }
11369             }
11370         }
11371
11372         if (appData.icsActive) {
11373             if (appData.quietPlay &&
11374                 (gameMode == IcsPlayingWhite ||
11375                  gameMode == IcsPlayingBlack)) {
11376                 SendToICS(ics_prefix);
11377                 SendToICS("set shout 1\n");
11378             }
11379             nextGameMode = IcsIdle;
11380             ics_user_moved = FALSE;
11381             /* clean up premove.  It's ugly when the game has ended and the
11382              * premove highlights are still on the board.
11383              */
11384             if (gotPremove) {
11385               gotPremove = FALSE;
11386               ClearPremoveHighlights();
11387               DrawPosition(FALSE, boards[currentMove]);
11388             }
11389             if (whosays == GE_ICS) {
11390                 switch (result) {
11391                 case WhiteWins:
11392                     if (gameMode == IcsPlayingWhite)
11393                         PlayIcsWinSound();
11394                     else if(gameMode == IcsPlayingBlack)
11395                         PlayIcsLossSound();
11396                     break;
11397                 case BlackWins:
11398                     if (gameMode == IcsPlayingBlack)
11399                         PlayIcsWinSound();
11400                     else if(gameMode == IcsPlayingWhite)
11401                         PlayIcsLossSound();
11402                     break;
11403                 case GameIsDrawn:
11404                     PlayIcsDrawSound();
11405                     break;
11406                 default:
11407                     PlayIcsUnfinishedSound();
11408                 }
11409             }
11410             if(appData.quitNext) { ExitEvent(0); return; }
11411         } else if (gameMode == EditGame ||
11412                    gameMode == PlayFromGameFile ||
11413                    gameMode == AnalyzeMode ||
11414                    gameMode == AnalyzeFile) {
11415             nextGameMode = gameMode;
11416         } else {
11417             nextGameMode = EndOfGame;
11418         }
11419         pausing = FALSE;
11420         ModeHighlight();
11421     } else {
11422         nextGameMode = gameMode;
11423     }
11424
11425     if (appData.noChessProgram) {
11426         gameMode = nextGameMode;
11427         ModeHighlight();
11428         endingGame = 0; /* [HGM] crash */
11429         return;
11430     }
11431
11432     if (first.reuse) {
11433         /* Put first chess program into idle state */
11434         if (first.pr != NoProc &&
11435             (gameMode == MachinePlaysWhite ||
11436              gameMode == MachinePlaysBlack ||
11437              gameMode == TwoMachinesPlay ||
11438              gameMode == IcsPlayingWhite ||
11439              gameMode == IcsPlayingBlack ||
11440              gameMode == BeginningOfGame)) {
11441             SendToProgram("force\n", &first);
11442             if (first.usePing) {
11443               char buf[MSG_SIZ];
11444               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11445               SendToProgram(buf, &first);
11446             }
11447         }
11448     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11449         /* Kill off first chess program */
11450         if (first.isr != NULL)
11451           RemoveInputSource(first.isr);
11452         first.isr = NULL;
11453
11454         if (first.pr != NoProc) {
11455             ExitAnalyzeMode();
11456             DoSleep( appData.delayBeforeQuit );
11457             SendToProgram("quit\n", &first);
11458             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11459             first.reload = TRUE;
11460         }
11461         first.pr = NoProc;
11462     }
11463     if (second.reuse) {
11464         /* Put second chess program into idle state */
11465         if (second.pr != NoProc &&
11466             gameMode == TwoMachinesPlay) {
11467             SendToProgram("force\n", &second);
11468             if (second.usePing) {
11469               char buf[MSG_SIZ];
11470               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11471               SendToProgram(buf, &second);
11472             }
11473         }
11474     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11475         /* Kill off second chess program */
11476         if (second.isr != NULL)
11477           RemoveInputSource(second.isr);
11478         second.isr = NULL;
11479
11480         if (second.pr != NoProc) {
11481             DoSleep( appData.delayBeforeQuit );
11482             SendToProgram("quit\n", &second);
11483             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11484             second.reload = TRUE;
11485         }
11486         second.pr = NoProc;
11487     }
11488
11489     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11490         char resChar = '=';
11491         switch (result) {
11492         case WhiteWins:
11493           resChar = '+';
11494           if (first.twoMachinesColor[0] == 'w') {
11495             first.matchWins++;
11496           } else {
11497             second.matchWins++;
11498           }
11499           break;
11500         case BlackWins:
11501           resChar = '-';
11502           if (first.twoMachinesColor[0] == 'b') {
11503             first.matchWins++;
11504           } else {
11505             second.matchWins++;
11506           }
11507           break;
11508         case GameUnfinished:
11509           resChar = ' ';
11510         default:
11511           break;
11512         }
11513
11514         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11515         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11516             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11517             ReserveGame(nextGame, resChar); // sets nextGame
11518             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11519             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11520         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11521
11522         if (nextGame <= appData.matchGames && !abortMatch) {
11523             gameMode = nextGameMode;
11524             matchGame = nextGame; // this will be overruled in tourney mode!
11525             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11526             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11527             endingGame = 0; /* [HGM] crash */
11528             return;
11529         } else {
11530             gameMode = nextGameMode;
11531             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11532                      first.tidy, second.tidy,
11533                      first.matchWins, second.matchWins,
11534                      appData.matchGames - (first.matchWins + second.matchWins));
11535             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11536             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11537             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11538             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11539                 first.twoMachinesColor = "black\n";
11540                 second.twoMachinesColor = "white\n";
11541             } else {
11542                 first.twoMachinesColor = "white\n";
11543                 second.twoMachinesColor = "black\n";
11544             }
11545         }
11546     }
11547     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11548         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11549       ExitAnalyzeMode();
11550     gameMode = nextGameMode;
11551     ModeHighlight();
11552     endingGame = 0;  /* [HGM] crash */
11553     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11554         if(matchMode == TRUE) { // match through command line: exit with or without popup
11555             if(ranking) {
11556                 ToNrEvent(forwardMostMove);
11557                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11558                 else ExitEvent(0);
11559             } else DisplayFatalError(buf, 0, 0);
11560         } else { // match through menu; just stop, with or without popup
11561             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11562             ModeHighlight();
11563             if(ranking){
11564                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11565             } else DisplayNote(buf);
11566       }
11567       if(ranking) free(ranking);
11568     }
11569 }
11570
11571 /* Assumes program was just initialized (initString sent).
11572    Leaves program in force mode. */
11573 void
11574 FeedMovesToProgram (ChessProgramState *cps, int upto)
11575 {
11576     int i;
11577
11578     if (appData.debugMode)
11579       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11580               startedFromSetupPosition ? "position and " : "",
11581               backwardMostMove, upto, cps->which);
11582     if(currentlyInitializedVariant != gameInfo.variant) {
11583       char buf[MSG_SIZ];
11584         // [HGM] variantswitch: make engine aware of new variant
11585         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11586                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11587                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11588         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11589         SendToProgram(buf, cps);
11590         currentlyInitializedVariant = gameInfo.variant;
11591     }
11592     SendToProgram("force\n", cps);
11593     if (startedFromSetupPosition) {
11594         SendBoard(cps, backwardMostMove);
11595     if (appData.debugMode) {
11596         fprintf(debugFP, "feedMoves\n");
11597     }
11598     }
11599     for (i = backwardMostMove; i < upto; i++) {
11600         SendMoveToProgram(i, cps);
11601     }
11602 }
11603
11604
11605 int
11606 ResurrectChessProgram ()
11607 {
11608      /* The chess program may have exited.
11609         If so, restart it and feed it all the moves made so far. */
11610     static int doInit = 0;
11611
11612     if (appData.noChessProgram) return 1;
11613
11614     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11615         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11616         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11617         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11618     } else {
11619         if (first.pr != NoProc) return 1;
11620         StartChessProgram(&first);
11621     }
11622     InitChessProgram(&first, FALSE);
11623     FeedMovesToProgram(&first, currentMove);
11624
11625     if (!first.sendTime) {
11626         /* can't tell gnuchess what its clock should read,
11627            so we bow to its notion. */
11628         ResetClocks();
11629         timeRemaining[0][currentMove] = whiteTimeRemaining;
11630         timeRemaining[1][currentMove] = blackTimeRemaining;
11631     }
11632
11633     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11634                 appData.icsEngineAnalyze) && first.analysisSupport) {
11635       SendToProgram("analyze\n", &first);
11636       first.analyzing = TRUE;
11637     }
11638     return 1;
11639 }
11640
11641 /*
11642  * Button procedures
11643  */
11644 void
11645 Reset (int redraw, int init)
11646 {
11647     int i;
11648
11649     if (appData.debugMode) {
11650         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11651                 redraw, init, gameMode);
11652     }
11653     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11654     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11655     CleanupTail(); // [HGM] vari: delete any stored variations
11656     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11657     pausing = pauseExamInvalid = FALSE;
11658     startedFromSetupPosition = blackPlaysFirst = FALSE;
11659     firstMove = TRUE;
11660     whiteFlag = blackFlag = FALSE;
11661     userOfferedDraw = FALSE;
11662     hintRequested = bookRequested = FALSE;
11663     first.maybeThinking = FALSE;
11664     second.maybeThinking = FALSE;
11665     first.bookSuspend = FALSE; // [HGM] book
11666     second.bookSuspend = FALSE;
11667     thinkOutput[0] = NULLCHAR;
11668     lastHint[0] = NULLCHAR;
11669     ClearGameInfo(&gameInfo);
11670     gameInfo.variant = StringToVariant(appData.variant);
11671     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11672     ics_user_moved = ics_clock_paused = FALSE;
11673     ics_getting_history = H_FALSE;
11674     ics_gamenum = -1;
11675     white_holding[0] = black_holding[0] = NULLCHAR;
11676     ClearProgramStats();
11677     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11678
11679     ResetFrontEnd();
11680     ClearHighlights();
11681     flipView = appData.flipView;
11682     ClearPremoveHighlights();
11683     gotPremove = FALSE;
11684     alarmSounded = FALSE;
11685     killX = killY = -1; // [HGM] lion
11686
11687     GameEnds(EndOfFile, NULL, GE_PLAYER);
11688     if(appData.serverMovesName != NULL) {
11689         /* [HGM] prepare to make moves file for broadcasting */
11690         clock_t t = clock();
11691         if(serverMoves != NULL) fclose(serverMoves);
11692         serverMoves = fopen(appData.serverMovesName, "r");
11693         if(serverMoves != NULL) {
11694             fclose(serverMoves);
11695             /* delay 15 sec before overwriting, so all clients can see end */
11696             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11697         }
11698         serverMoves = fopen(appData.serverMovesName, "w");
11699     }
11700
11701     ExitAnalyzeMode();
11702     gameMode = BeginningOfGame;
11703     ModeHighlight();
11704     if(appData.icsActive) gameInfo.variant = VariantNormal;
11705     currentMove = forwardMostMove = backwardMostMove = 0;
11706     MarkTargetSquares(1);
11707     InitPosition(redraw);
11708     for (i = 0; i < MAX_MOVES; i++) {
11709         if (commentList[i] != NULL) {
11710             free(commentList[i]);
11711             commentList[i] = NULL;
11712         }
11713     }
11714     ResetClocks();
11715     timeRemaining[0][0] = whiteTimeRemaining;
11716     timeRemaining[1][0] = blackTimeRemaining;
11717
11718     if (first.pr == NoProc) {
11719         StartChessProgram(&first);
11720     }
11721     if (init) {
11722             InitChessProgram(&first, startedFromSetupPosition);
11723     }
11724     DisplayTitle("");
11725     DisplayMessage("", "");
11726     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11727     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11728     ClearMap();        // [HGM] exclude: invalidate map
11729 }
11730
11731 void
11732 AutoPlayGameLoop ()
11733 {
11734     for (;;) {
11735         if (!AutoPlayOneMove())
11736           return;
11737         if (matchMode || appData.timeDelay == 0)
11738           continue;
11739         if (appData.timeDelay < 0)
11740           return;
11741         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11742         break;
11743     }
11744 }
11745
11746 void
11747 AnalyzeNextGame()
11748 {
11749     ReloadGame(1); // next game
11750 }
11751
11752 int
11753 AutoPlayOneMove ()
11754 {
11755     int fromX, fromY, toX, toY;
11756
11757     if (appData.debugMode) {
11758       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11759     }
11760
11761     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11762       return FALSE;
11763
11764     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11765       pvInfoList[currentMove].depth = programStats.depth;
11766       pvInfoList[currentMove].score = programStats.score;
11767       pvInfoList[currentMove].time  = 0;
11768       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11769       else { // append analysis of final position as comment
11770         char buf[MSG_SIZ];
11771         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11772         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11773       }
11774       programStats.depth = 0;
11775     }
11776
11777     if (currentMove >= forwardMostMove) {
11778       if(gameMode == AnalyzeFile) {
11779           if(appData.loadGameIndex == -1) {
11780             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11781           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11782           } else {
11783           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11784         }
11785       }
11786 //      gameMode = EndOfGame;
11787 //      ModeHighlight();
11788
11789       /* [AS] Clear current move marker at the end of a game */
11790       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11791
11792       return FALSE;
11793     }
11794
11795     toX = moveList[currentMove][2] - AAA;
11796     toY = moveList[currentMove][3] - ONE;
11797
11798     if (moveList[currentMove][1] == '@') {
11799         if (appData.highlightLastMove) {
11800             SetHighlights(-1, -1, toX, toY);
11801         }
11802     } else {
11803         int viaX = moveList[currentMove][5] - AAA;
11804         int viaY = moveList[currentMove][6] - ONE;
11805         fromX = moveList[currentMove][0] - AAA;
11806         fromY = moveList[currentMove][1] - ONE;
11807
11808         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11809
11810         if(moveList[currentMove][4] == ';') { // multi-leg
11811             ChessSquare piece = boards[currentMove][viaY][viaX];
11812             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11813             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11814             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11815             boards[currentMove][viaY][viaX] = piece;
11816         } else
11817         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11818
11819         if (appData.highlightLastMove) {
11820             SetHighlights(fromX, fromY, toX, toY);
11821         }
11822     }
11823     DisplayMove(currentMove);
11824     SendMoveToProgram(currentMove++, &first);
11825     DisplayBothClocks();
11826     DrawPosition(FALSE, boards[currentMove]);
11827     // [HGM] PV info: always display, routine tests if empty
11828     DisplayComment(currentMove - 1, commentList[currentMove]);
11829     return TRUE;
11830 }
11831
11832
11833 int
11834 LoadGameOneMove (ChessMove readAhead)
11835 {
11836     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11837     char promoChar = NULLCHAR;
11838     ChessMove moveType;
11839     char move[MSG_SIZ];
11840     char *p, *q;
11841
11842     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11843         gameMode != AnalyzeMode && gameMode != Training) {
11844         gameFileFP = NULL;
11845         return FALSE;
11846     }
11847
11848     yyboardindex = forwardMostMove;
11849     if (readAhead != EndOfFile) {
11850       moveType = readAhead;
11851     } else {
11852       if (gameFileFP == NULL)
11853           return FALSE;
11854       moveType = (ChessMove) Myylex();
11855     }
11856
11857     done = FALSE;
11858     switch (moveType) {
11859       case Comment:
11860         if (appData.debugMode)
11861           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11862         p = yy_text;
11863
11864         /* append the comment but don't display it */
11865         AppendComment(currentMove, p, FALSE);
11866         return TRUE;
11867
11868       case WhiteCapturesEnPassant:
11869       case BlackCapturesEnPassant:
11870       case WhitePromotion:
11871       case BlackPromotion:
11872       case WhiteNonPromotion:
11873       case BlackNonPromotion:
11874       case NormalMove:
11875       case FirstLeg:
11876       case WhiteKingSideCastle:
11877       case WhiteQueenSideCastle:
11878       case BlackKingSideCastle:
11879       case BlackQueenSideCastle:
11880       case WhiteKingSideCastleWild:
11881       case WhiteQueenSideCastleWild:
11882       case BlackKingSideCastleWild:
11883       case BlackQueenSideCastleWild:
11884       /* PUSH Fabien */
11885       case WhiteHSideCastleFR:
11886       case WhiteASideCastleFR:
11887       case BlackHSideCastleFR:
11888       case BlackASideCastleFR:
11889       /* POP Fabien */
11890         if (appData.debugMode)
11891           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11892         fromX = currentMoveString[0] - AAA;
11893         fromY = currentMoveString[1] - ONE;
11894         toX = currentMoveString[2] - AAA;
11895         toY = currentMoveString[3] - ONE;
11896         promoChar = currentMoveString[4];
11897         if(promoChar == ';') promoChar = NULLCHAR;
11898         break;
11899
11900       case WhiteDrop:
11901       case BlackDrop:
11902         if (appData.debugMode)
11903           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11904         fromX = moveType == WhiteDrop ?
11905           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11906         (int) CharToPiece(ToLower(currentMoveString[0]));
11907         fromY = DROP_RANK;
11908         toX = currentMoveString[2] - AAA;
11909         toY = currentMoveString[3] - ONE;
11910         break;
11911
11912       case WhiteWins:
11913       case BlackWins:
11914       case GameIsDrawn:
11915       case GameUnfinished:
11916         if (appData.debugMode)
11917           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11918         p = strchr(yy_text, '{');
11919         if (p == NULL) p = strchr(yy_text, '(');
11920         if (p == NULL) {
11921             p = yy_text;
11922             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11923         } else {
11924             q = strchr(p, *p == '{' ? '}' : ')');
11925             if (q != NULL) *q = NULLCHAR;
11926             p++;
11927         }
11928         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11929         GameEnds(moveType, p, GE_FILE);
11930         done = TRUE;
11931         if (cmailMsgLoaded) {
11932             ClearHighlights();
11933             flipView = WhiteOnMove(currentMove);
11934             if (moveType == GameUnfinished) flipView = !flipView;
11935             if (appData.debugMode)
11936               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11937         }
11938         break;
11939
11940       case EndOfFile:
11941         if (appData.debugMode)
11942           fprintf(debugFP, "Parser hit end of file\n");
11943         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11944           case MT_NONE:
11945           case MT_CHECK:
11946             break;
11947           case MT_CHECKMATE:
11948           case MT_STAINMATE:
11949             if (WhiteOnMove(currentMove)) {
11950                 GameEnds(BlackWins, "Black mates", GE_FILE);
11951             } else {
11952                 GameEnds(WhiteWins, "White mates", GE_FILE);
11953             }
11954             break;
11955           case MT_STALEMATE:
11956             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11957             break;
11958         }
11959         done = TRUE;
11960         break;
11961
11962       case MoveNumberOne:
11963         if (lastLoadGameStart == GNUChessGame) {
11964             /* GNUChessGames have numbers, but they aren't move numbers */
11965             if (appData.debugMode)
11966               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11967                       yy_text, (int) moveType);
11968             return LoadGameOneMove(EndOfFile); /* tail recursion */
11969         }
11970         /* else fall thru */
11971
11972       case XBoardGame:
11973       case GNUChessGame:
11974       case PGNTag:
11975         /* Reached start of next game in file */
11976         if (appData.debugMode)
11977           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11978         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11979           case MT_NONE:
11980           case MT_CHECK:
11981             break;
11982           case MT_CHECKMATE:
11983           case MT_STAINMATE:
11984             if (WhiteOnMove(currentMove)) {
11985                 GameEnds(BlackWins, "Black mates", GE_FILE);
11986             } else {
11987                 GameEnds(WhiteWins, "White mates", GE_FILE);
11988             }
11989             break;
11990           case MT_STALEMATE:
11991             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11992             break;
11993         }
11994         done = TRUE;
11995         break;
11996
11997       case PositionDiagram:     /* should not happen; ignore */
11998       case ElapsedTime:         /* ignore */
11999       case NAG:                 /* ignore */
12000         if (appData.debugMode)
12001           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12002                   yy_text, (int) moveType);
12003         return LoadGameOneMove(EndOfFile); /* tail recursion */
12004
12005       case IllegalMove:
12006         if (appData.testLegality) {
12007             if (appData.debugMode)
12008               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12009             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12010                     (forwardMostMove / 2) + 1,
12011                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12012             DisplayError(move, 0);
12013             done = TRUE;
12014         } else {
12015             if (appData.debugMode)
12016               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12017                       yy_text, currentMoveString);
12018             fromX = currentMoveString[0] - AAA;
12019             fromY = currentMoveString[1] - ONE;
12020             toX = currentMoveString[2] - AAA;
12021             toY = currentMoveString[3] - ONE;
12022             promoChar = currentMoveString[4];
12023         }
12024         break;
12025
12026       case AmbiguousMove:
12027         if (appData.debugMode)
12028           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12029         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12030                 (forwardMostMove / 2) + 1,
12031                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12032         DisplayError(move, 0);
12033         done = TRUE;
12034         break;
12035
12036       default:
12037       case ImpossibleMove:
12038         if (appData.debugMode)
12039           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12040         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12041                 (forwardMostMove / 2) + 1,
12042                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12043         DisplayError(move, 0);
12044         done = TRUE;
12045         break;
12046     }
12047
12048     if (done) {
12049         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12050             DrawPosition(FALSE, boards[currentMove]);
12051             DisplayBothClocks();
12052             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12053               DisplayComment(currentMove - 1, commentList[currentMove]);
12054         }
12055         (void) StopLoadGameTimer();
12056         gameFileFP = NULL;
12057         cmailOldMove = forwardMostMove;
12058         return FALSE;
12059     } else {
12060         /* currentMoveString is set as a side-effect of yylex */
12061
12062         thinkOutput[0] = NULLCHAR;
12063         MakeMove(fromX, fromY, toX, toY, promoChar);
12064         killX = killY = -1; // [HGM] lion: used up
12065         currentMove = forwardMostMove;
12066         return TRUE;
12067     }
12068 }
12069
12070 /* Load the nth game from the given file */
12071 int
12072 LoadGameFromFile (char *filename, int n, char *title, int useList)
12073 {
12074     FILE *f;
12075     char buf[MSG_SIZ];
12076
12077     if (strcmp(filename, "-") == 0) {
12078         f = stdin;
12079         title = "stdin";
12080     } else {
12081         f = fopen(filename, "rb");
12082         if (f == NULL) {
12083           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12084             DisplayError(buf, errno);
12085             return FALSE;
12086         }
12087     }
12088     if (fseek(f, 0, 0) == -1) {
12089         /* f is not seekable; probably a pipe */
12090         useList = FALSE;
12091     }
12092     if (useList && n == 0) {
12093         int error = GameListBuild(f);
12094         if (error) {
12095             DisplayError(_("Cannot build game list"), error);
12096         } else if (!ListEmpty(&gameList) &&
12097                    ((ListGame *) gameList.tailPred)->number > 1) {
12098             GameListPopUp(f, title);
12099             return TRUE;
12100         }
12101         GameListDestroy();
12102         n = 1;
12103     }
12104     if (n == 0) n = 1;
12105     return LoadGame(f, n, title, FALSE);
12106 }
12107
12108
12109 void
12110 MakeRegisteredMove ()
12111 {
12112     int fromX, fromY, toX, toY;
12113     char promoChar;
12114     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12115         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12116           case CMAIL_MOVE:
12117           case CMAIL_DRAW:
12118             if (appData.debugMode)
12119               fprintf(debugFP, "Restoring %s for game %d\n",
12120                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12121
12122             thinkOutput[0] = NULLCHAR;
12123             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12124             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12125             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12126             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12127             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12128             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12129             MakeMove(fromX, fromY, toX, toY, promoChar);
12130             ShowMove(fromX, fromY, toX, toY);
12131
12132             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12133               case MT_NONE:
12134               case MT_CHECK:
12135                 break;
12136
12137               case MT_CHECKMATE:
12138               case MT_STAINMATE:
12139                 if (WhiteOnMove(currentMove)) {
12140                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12141                 } else {
12142                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12143                 }
12144                 break;
12145
12146               case MT_STALEMATE:
12147                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12148                 break;
12149             }
12150
12151             break;
12152
12153           case CMAIL_RESIGN:
12154             if (WhiteOnMove(currentMove)) {
12155                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12156             } else {
12157                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12158             }
12159             break;
12160
12161           case CMAIL_ACCEPT:
12162             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12163             break;
12164
12165           default:
12166             break;
12167         }
12168     }
12169
12170     return;
12171 }
12172
12173 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12174 int
12175 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12176 {
12177     int retVal;
12178
12179     if (gameNumber > nCmailGames) {
12180         DisplayError(_("No more games in this message"), 0);
12181         return FALSE;
12182     }
12183     if (f == lastLoadGameFP) {
12184         int offset = gameNumber - lastLoadGameNumber;
12185         if (offset == 0) {
12186             cmailMsg[0] = NULLCHAR;
12187             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12188                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12189                 nCmailMovesRegistered--;
12190             }
12191             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12192             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12193                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12194             }
12195         } else {
12196             if (! RegisterMove()) return FALSE;
12197         }
12198     }
12199
12200     retVal = LoadGame(f, gameNumber, title, useList);
12201
12202     /* Make move registered during previous look at this game, if any */
12203     MakeRegisteredMove();
12204
12205     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12206         commentList[currentMove]
12207           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12208         DisplayComment(currentMove - 1, commentList[currentMove]);
12209     }
12210
12211     return retVal;
12212 }
12213
12214 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12215 int
12216 ReloadGame (int offset)
12217 {
12218     int gameNumber = lastLoadGameNumber + offset;
12219     if (lastLoadGameFP == NULL) {
12220         DisplayError(_("No game has been loaded yet"), 0);
12221         return FALSE;
12222     }
12223     if (gameNumber <= 0) {
12224         DisplayError(_("Can't back up any further"), 0);
12225         return FALSE;
12226     }
12227     if (cmailMsgLoaded) {
12228         return CmailLoadGame(lastLoadGameFP, gameNumber,
12229                              lastLoadGameTitle, lastLoadGameUseList);
12230     } else {
12231         return LoadGame(lastLoadGameFP, gameNumber,
12232                         lastLoadGameTitle, lastLoadGameUseList);
12233     }
12234 }
12235
12236 int keys[EmptySquare+1];
12237
12238 int
12239 PositionMatches (Board b1, Board b2)
12240 {
12241     int r, f, sum=0;
12242     switch(appData.searchMode) {
12243         case 1: return CompareWithRights(b1, b2);
12244         case 2:
12245             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12246                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12247             }
12248             return TRUE;
12249         case 3:
12250             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12251               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12252                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12253             }
12254             return sum==0;
12255         case 4:
12256             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12257                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12258             }
12259             return sum==0;
12260     }
12261     return TRUE;
12262 }
12263
12264 #define Q_PROMO  4
12265 #define Q_EP     3
12266 #define Q_BCASTL 2
12267 #define Q_WCASTL 1
12268
12269 int pieceList[256], quickBoard[256];
12270 ChessSquare pieceType[256] = { EmptySquare };
12271 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12272 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12273 int soughtTotal, turn;
12274 Boolean epOK, flipSearch;
12275
12276 typedef struct {
12277     unsigned char piece, to;
12278 } Move;
12279
12280 #define DSIZE (250000)
12281
12282 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12283 Move *moveDatabase = initialSpace;
12284 unsigned int movePtr, dataSize = DSIZE;
12285
12286 int
12287 MakePieceList (Board board, int *counts)
12288 {
12289     int r, f, n=Q_PROMO, total=0;
12290     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12291     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12292         int sq = f + (r<<4);
12293         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12294             quickBoard[sq] = ++n;
12295             pieceList[n] = sq;
12296             pieceType[n] = board[r][f];
12297             counts[board[r][f]]++;
12298             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12299             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12300             total++;
12301         }
12302     }
12303     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12304     return total;
12305 }
12306
12307 void
12308 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12309 {
12310     int sq = fromX + (fromY<<4);
12311     int piece = quickBoard[sq], rook;
12312     quickBoard[sq] = 0;
12313     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12314     if(piece == pieceList[1] && fromY == toY) {
12315       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12316         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12317         moveDatabase[movePtr++].piece = Q_WCASTL;
12318         quickBoard[sq] = piece;
12319         piece = quickBoard[from]; quickBoard[from] = 0;
12320         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12321       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12322         quickBoard[sq] = 0; // remove Rook
12323         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12324         moveDatabase[movePtr++].piece = Q_WCASTL;
12325         quickBoard[sq] = pieceList[1]; // put King
12326         piece = rook;
12327         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12328       }
12329     } else
12330     if(piece == pieceList[2] && fromY == toY) {
12331       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12332         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12333         moveDatabase[movePtr++].piece = Q_BCASTL;
12334         quickBoard[sq] = piece;
12335         piece = quickBoard[from]; quickBoard[from] = 0;
12336         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12337       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12338         quickBoard[sq] = 0; // remove Rook
12339         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12340         moveDatabase[movePtr++].piece = Q_BCASTL;
12341         quickBoard[sq] = pieceList[2]; // put King
12342         piece = rook;
12343         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12344       }
12345     } else
12346     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12347         quickBoard[(fromY<<4)+toX] = 0;
12348         moveDatabase[movePtr].piece = Q_EP;
12349         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12350         moveDatabase[movePtr].to = sq;
12351     } else
12352     if(promoPiece != pieceType[piece]) {
12353         moveDatabase[movePtr++].piece = Q_PROMO;
12354         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12355     }
12356     moveDatabase[movePtr].piece = piece;
12357     quickBoard[sq] = piece;
12358     movePtr++;
12359 }
12360
12361 int
12362 PackGame (Board board)
12363 {
12364     Move *newSpace = NULL;
12365     moveDatabase[movePtr].piece = 0; // terminate previous game
12366     if(movePtr > dataSize) {
12367         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12368         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12369         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12370         if(newSpace) {
12371             int i;
12372             Move *p = moveDatabase, *q = newSpace;
12373             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12374             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12375             moveDatabase = newSpace;
12376         } else { // calloc failed, we must be out of memory. Too bad...
12377             dataSize = 0; // prevent calloc events for all subsequent games
12378             return 0;     // and signal this one isn't cached
12379         }
12380     }
12381     movePtr++;
12382     MakePieceList(board, counts);
12383     return movePtr;
12384 }
12385
12386 int
12387 QuickCompare (Board board, int *minCounts, int *maxCounts)
12388 {   // compare according to search mode
12389     int r, f;
12390     switch(appData.searchMode)
12391     {
12392       case 1: // exact position match
12393         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12394         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12395             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12396         }
12397         break;
12398       case 2: // can have extra material on empty squares
12399         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12400             if(board[r][f] == EmptySquare) continue;
12401             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12402         }
12403         break;
12404       case 3: // material with exact Pawn structure
12405         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12406             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12407             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12408         } // fall through to material comparison
12409       case 4: // exact material
12410         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12411         break;
12412       case 6: // material range with given imbalance
12413         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12414         // fall through to range comparison
12415       case 5: // material range
12416         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12417     }
12418     return TRUE;
12419 }
12420
12421 int
12422 QuickScan (Board board, Move *move)
12423 {   // reconstruct game,and compare all positions in it
12424     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12425     do {
12426         int piece = move->piece;
12427         int to = move->to, from = pieceList[piece];
12428         if(found < 0) { // if already found just scan to game end for final piece count
12429           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12430            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12431            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12432                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12433             ) {
12434             static int lastCounts[EmptySquare+1];
12435             int i;
12436             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12437             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12438           } else stretch = 0;
12439           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12440           if(found >= 0 && !appData.minPieces) return found;
12441         }
12442         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12443           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12444           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12445             piece = (++move)->piece;
12446             from = pieceList[piece];
12447             counts[pieceType[piece]]--;
12448             pieceType[piece] = (ChessSquare) move->to;
12449             counts[move->to]++;
12450           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12451             counts[pieceType[quickBoard[to]]]--;
12452             quickBoard[to] = 0; total--;
12453             move++;
12454             continue;
12455           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12456             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12457             from  = pieceList[piece]; // so this must be King
12458             quickBoard[from] = 0;
12459             pieceList[piece] = to;
12460             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12461             quickBoard[from] = 0; // rook
12462             quickBoard[to] = piece;
12463             to = move->to; piece = move->piece;
12464             goto aftercastle;
12465           }
12466         }
12467         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12468         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12469         quickBoard[from] = 0;
12470       aftercastle:
12471         quickBoard[to] = piece;
12472         pieceList[piece] = to;
12473         cnt++; turn ^= 3;
12474         move++;
12475     } while(1);
12476 }
12477
12478 void
12479 InitSearch ()
12480 {
12481     int r, f;
12482     flipSearch = FALSE;
12483     CopyBoard(soughtBoard, boards[currentMove]);
12484     soughtTotal = MakePieceList(soughtBoard, maxSought);
12485     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12486     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12487     CopyBoard(reverseBoard, boards[currentMove]);
12488     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12489         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12490         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12491         reverseBoard[r][f] = piece;
12492     }
12493     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12494     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12495     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12496                  || (boards[currentMove][CASTLING][2] == NoRights ||
12497                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12498                  && (boards[currentMove][CASTLING][5] == NoRights ||
12499                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12500       ) {
12501         flipSearch = TRUE;
12502         CopyBoard(flipBoard, soughtBoard);
12503         CopyBoard(rotateBoard, reverseBoard);
12504         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12505             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12506             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12507         }
12508     }
12509     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12510     if(appData.searchMode >= 5) {
12511         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12512         MakePieceList(soughtBoard, minSought);
12513         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12514     }
12515     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12516         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12517 }
12518
12519 GameInfo dummyInfo;
12520 static int creatingBook;
12521
12522 int
12523 GameContainsPosition (FILE *f, ListGame *lg)
12524 {
12525     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12526     int fromX, fromY, toX, toY;
12527     char promoChar;
12528     static int initDone=FALSE;
12529
12530     // weed out games based on numerical tag comparison
12531     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12532     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12533     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12534     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12535     if(!initDone) {
12536         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12537         initDone = TRUE;
12538     }
12539     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12540     else CopyBoard(boards[scratch], initialPosition); // default start position
12541     if(lg->moves) {
12542         turn = btm + 1;
12543         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12544         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12545     }
12546     if(btm) plyNr++;
12547     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12548     fseek(f, lg->offset, 0);
12549     yynewfile(f);
12550     while(1) {
12551         yyboardindex = scratch;
12552         quickFlag = plyNr+1;
12553         next = Myylex();
12554         quickFlag = 0;
12555         switch(next) {
12556             case PGNTag:
12557                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12558             default:
12559                 continue;
12560
12561             case XBoardGame:
12562             case GNUChessGame:
12563                 if(plyNr) return -1; // after we have seen moves, this is for new game
12564               continue;
12565
12566             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12567             case ImpossibleMove:
12568             case WhiteWins: // game ends here with these four
12569             case BlackWins:
12570             case GameIsDrawn:
12571             case GameUnfinished:
12572                 return -1;
12573
12574             case IllegalMove:
12575                 if(appData.testLegality) return -1;
12576             case WhiteCapturesEnPassant:
12577             case BlackCapturesEnPassant:
12578             case WhitePromotion:
12579             case BlackPromotion:
12580             case WhiteNonPromotion:
12581             case BlackNonPromotion:
12582             case NormalMove:
12583             case FirstLeg:
12584             case WhiteKingSideCastle:
12585             case WhiteQueenSideCastle:
12586             case BlackKingSideCastle:
12587             case BlackQueenSideCastle:
12588             case WhiteKingSideCastleWild:
12589             case WhiteQueenSideCastleWild:
12590             case BlackKingSideCastleWild:
12591             case BlackQueenSideCastleWild:
12592             case WhiteHSideCastleFR:
12593             case WhiteASideCastleFR:
12594             case BlackHSideCastleFR:
12595             case BlackASideCastleFR:
12596                 fromX = currentMoveString[0] - AAA;
12597                 fromY = currentMoveString[1] - ONE;
12598                 toX = currentMoveString[2] - AAA;
12599                 toY = currentMoveString[3] - ONE;
12600                 promoChar = currentMoveString[4];
12601                 break;
12602             case WhiteDrop:
12603             case BlackDrop:
12604                 fromX = next == WhiteDrop ?
12605                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12606                   (int) CharToPiece(ToLower(currentMoveString[0]));
12607                 fromY = DROP_RANK;
12608                 toX = currentMoveString[2] - AAA;
12609                 toY = currentMoveString[3] - ONE;
12610                 promoChar = 0;
12611                 break;
12612         }
12613         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12614         plyNr++;
12615         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12616         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12617         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12618         if(appData.findMirror) {
12619             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12620             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12621         }
12622     }
12623 }
12624
12625 /* Load the nth game from open file f */
12626 int
12627 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12628 {
12629     ChessMove cm;
12630     char buf[MSG_SIZ];
12631     int gn = gameNumber;
12632     ListGame *lg = NULL;
12633     int numPGNTags = 0;
12634     int err, pos = -1;
12635     GameMode oldGameMode;
12636     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12637
12638     if (appData.debugMode)
12639         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12640
12641     if (gameMode == Training )
12642         SetTrainingModeOff();
12643
12644     oldGameMode = gameMode;
12645     if (gameMode != BeginningOfGame) {
12646       Reset(FALSE, TRUE);
12647     }
12648     killX = killY = -1; // [HGM] lion: in case we did not Reset
12649
12650     gameFileFP = f;
12651     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12652         fclose(lastLoadGameFP);
12653     }
12654
12655     if (useList) {
12656         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12657
12658         if (lg) {
12659             fseek(f, lg->offset, 0);
12660             GameListHighlight(gameNumber);
12661             pos = lg->position;
12662             gn = 1;
12663         }
12664         else {
12665             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12666               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12667             else
12668             DisplayError(_("Game number out of range"), 0);
12669             return FALSE;
12670         }
12671     } else {
12672         GameListDestroy();
12673         if (fseek(f, 0, 0) == -1) {
12674             if (f == lastLoadGameFP ?
12675                 gameNumber == lastLoadGameNumber + 1 :
12676                 gameNumber == 1) {
12677                 gn = 1;
12678             } else {
12679                 DisplayError(_("Can't seek on game file"), 0);
12680                 return FALSE;
12681             }
12682         }
12683     }
12684     lastLoadGameFP = f;
12685     lastLoadGameNumber = gameNumber;
12686     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12687     lastLoadGameUseList = useList;
12688
12689     yynewfile(f);
12690
12691     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12692       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12693                 lg->gameInfo.black);
12694             DisplayTitle(buf);
12695     } else if (*title != NULLCHAR) {
12696         if (gameNumber > 1) {
12697           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12698             DisplayTitle(buf);
12699         } else {
12700             DisplayTitle(title);
12701         }
12702     }
12703
12704     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12705         gameMode = PlayFromGameFile;
12706         ModeHighlight();
12707     }
12708
12709     currentMove = forwardMostMove = backwardMostMove = 0;
12710     CopyBoard(boards[0], initialPosition);
12711     StopClocks();
12712
12713     /*
12714      * Skip the first gn-1 games in the file.
12715      * Also skip over anything that precedes an identifiable
12716      * start of game marker, to avoid being confused by
12717      * garbage at the start of the file.  Currently
12718      * recognized start of game markers are the move number "1",
12719      * the pattern "gnuchess .* game", the pattern
12720      * "^[#;%] [^ ]* game file", and a PGN tag block.
12721      * A game that starts with one of the latter two patterns
12722      * will also have a move number 1, possibly
12723      * following a position diagram.
12724      * 5-4-02: Let's try being more lenient and allowing a game to
12725      * start with an unnumbered move.  Does that break anything?
12726      */
12727     cm = lastLoadGameStart = EndOfFile;
12728     while (gn > 0) {
12729         yyboardindex = forwardMostMove;
12730         cm = (ChessMove) Myylex();
12731         switch (cm) {
12732           case EndOfFile:
12733             if (cmailMsgLoaded) {
12734                 nCmailGames = CMAIL_MAX_GAMES - gn;
12735             } else {
12736                 Reset(TRUE, TRUE);
12737                 DisplayError(_("Game not found in file"), 0);
12738             }
12739             return FALSE;
12740
12741           case GNUChessGame:
12742           case XBoardGame:
12743             gn--;
12744             lastLoadGameStart = cm;
12745             break;
12746
12747           case MoveNumberOne:
12748             switch (lastLoadGameStart) {
12749               case GNUChessGame:
12750               case XBoardGame:
12751               case PGNTag:
12752                 break;
12753               case MoveNumberOne:
12754               case EndOfFile:
12755                 gn--;           /* count this game */
12756                 lastLoadGameStart = cm;
12757                 break;
12758               default:
12759                 /* impossible */
12760                 break;
12761             }
12762             break;
12763
12764           case PGNTag:
12765             switch (lastLoadGameStart) {
12766               case GNUChessGame:
12767               case PGNTag:
12768               case MoveNumberOne:
12769               case EndOfFile:
12770                 gn--;           /* count this game */
12771                 lastLoadGameStart = cm;
12772                 break;
12773               case XBoardGame:
12774                 lastLoadGameStart = cm; /* game counted already */
12775                 break;
12776               default:
12777                 /* impossible */
12778                 break;
12779             }
12780             if (gn > 0) {
12781                 do {
12782                     yyboardindex = forwardMostMove;
12783                     cm = (ChessMove) Myylex();
12784                 } while (cm == PGNTag || cm == Comment);
12785             }
12786             break;
12787
12788           case WhiteWins:
12789           case BlackWins:
12790           case GameIsDrawn:
12791             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12792                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12793                     != CMAIL_OLD_RESULT) {
12794                     nCmailResults ++ ;
12795                     cmailResult[  CMAIL_MAX_GAMES
12796                                 - gn - 1] = CMAIL_OLD_RESULT;
12797                 }
12798             }
12799             break;
12800
12801           case NormalMove:
12802           case FirstLeg:
12803             /* Only a NormalMove can be at the start of a game
12804              * without a position diagram. */
12805             if (lastLoadGameStart == EndOfFile ) {
12806               gn--;
12807               lastLoadGameStart = MoveNumberOne;
12808             }
12809             break;
12810
12811           default:
12812             break;
12813         }
12814     }
12815
12816     if (appData.debugMode)
12817       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12818
12819     if (cm == XBoardGame) {
12820         /* Skip any header junk before position diagram and/or move 1 */
12821         for (;;) {
12822             yyboardindex = forwardMostMove;
12823             cm = (ChessMove) Myylex();
12824
12825             if (cm == EndOfFile ||
12826                 cm == GNUChessGame || cm == XBoardGame) {
12827                 /* Empty game; pretend end-of-file and handle later */
12828                 cm = EndOfFile;
12829                 break;
12830             }
12831
12832             if (cm == MoveNumberOne || cm == PositionDiagram ||
12833                 cm == PGNTag || cm == Comment)
12834               break;
12835         }
12836     } else if (cm == GNUChessGame) {
12837         if (gameInfo.event != NULL) {
12838             free(gameInfo.event);
12839         }
12840         gameInfo.event = StrSave(yy_text);
12841     }
12842
12843     startedFromSetupPosition = FALSE;
12844     while (cm == PGNTag) {
12845         if (appData.debugMode)
12846           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12847         err = ParsePGNTag(yy_text, &gameInfo);
12848         if (!err) numPGNTags++;
12849
12850         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12851         if(gameInfo.variant != oldVariant) {
12852             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12853             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12854             InitPosition(TRUE);
12855             oldVariant = gameInfo.variant;
12856             if (appData.debugMode)
12857               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12858         }
12859
12860
12861         if (gameInfo.fen != NULL) {
12862           Board initial_position;
12863           startedFromSetupPosition = TRUE;
12864           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12865             Reset(TRUE, TRUE);
12866             DisplayError(_("Bad FEN position in file"), 0);
12867             return FALSE;
12868           }
12869           CopyBoard(boards[0], initial_position);
12870           if (blackPlaysFirst) {
12871             currentMove = forwardMostMove = backwardMostMove = 1;
12872             CopyBoard(boards[1], initial_position);
12873             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12874             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12875             timeRemaining[0][1] = whiteTimeRemaining;
12876             timeRemaining[1][1] = blackTimeRemaining;
12877             if (commentList[0] != NULL) {
12878               commentList[1] = commentList[0];
12879               commentList[0] = NULL;
12880             }
12881           } else {
12882             currentMove = forwardMostMove = backwardMostMove = 0;
12883           }
12884           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12885           {   int i;
12886               initialRulePlies = FENrulePlies;
12887               for( i=0; i< nrCastlingRights; i++ )
12888                   initialRights[i] = initial_position[CASTLING][i];
12889           }
12890           yyboardindex = forwardMostMove;
12891           free(gameInfo.fen);
12892           gameInfo.fen = NULL;
12893         }
12894
12895         yyboardindex = forwardMostMove;
12896         cm = (ChessMove) Myylex();
12897
12898         /* Handle comments interspersed among the tags */
12899         while (cm == Comment) {
12900             char *p;
12901             if (appData.debugMode)
12902               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12903             p = yy_text;
12904             AppendComment(currentMove, p, FALSE);
12905             yyboardindex = forwardMostMove;
12906             cm = (ChessMove) Myylex();
12907         }
12908     }
12909
12910     /* don't rely on existence of Event tag since if game was
12911      * pasted from clipboard the Event tag may not exist
12912      */
12913     if (numPGNTags > 0){
12914         char *tags;
12915         if (gameInfo.variant == VariantNormal) {
12916           VariantClass v = StringToVariant(gameInfo.event);
12917           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12918           if(v < VariantShogi) gameInfo.variant = v;
12919         }
12920         if (!matchMode) {
12921           if( appData.autoDisplayTags ) {
12922             tags = PGNTags(&gameInfo);
12923             TagsPopUp(tags, CmailMsg());
12924             free(tags);
12925           }
12926         }
12927     } else {
12928         /* Make something up, but don't display it now */
12929         SetGameInfo();
12930         TagsPopDown();
12931     }
12932
12933     if (cm == PositionDiagram) {
12934         int i, j;
12935         char *p;
12936         Board initial_position;
12937
12938         if (appData.debugMode)
12939           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12940
12941         if (!startedFromSetupPosition) {
12942             p = yy_text;
12943             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12944               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12945                 switch (*p) {
12946                   case '{':
12947                   case '[':
12948                   case '-':
12949                   case ' ':
12950                   case '\t':
12951                   case '\n':
12952                   case '\r':
12953                     break;
12954                   default:
12955                     initial_position[i][j++] = CharToPiece(*p);
12956                     break;
12957                 }
12958             while (*p == ' ' || *p == '\t' ||
12959                    *p == '\n' || *p == '\r') p++;
12960
12961             if (strncmp(p, "black", strlen("black"))==0)
12962               blackPlaysFirst = TRUE;
12963             else
12964               blackPlaysFirst = FALSE;
12965             startedFromSetupPosition = TRUE;
12966
12967             CopyBoard(boards[0], initial_position);
12968             if (blackPlaysFirst) {
12969                 currentMove = forwardMostMove = backwardMostMove = 1;
12970                 CopyBoard(boards[1], initial_position);
12971                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12972                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12973                 timeRemaining[0][1] = whiteTimeRemaining;
12974                 timeRemaining[1][1] = blackTimeRemaining;
12975                 if (commentList[0] != NULL) {
12976                     commentList[1] = commentList[0];
12977                     commentList[0] = NULL;
12978                 }
12979             } else {
12980                 currentMove = forwardMostMove = backwardMostMove = 0;
12981             }
12982         }
12983         yyboardindex = forwardMostMove;
12984         cm = (ChessMove) Myylex();
12985     }
12986
12987   if(!creatingBook) {
12988     if (first.pr == NoProc) {
12989         StartChessProgram(&first);
12990     }
12991     InitChessProgram(&first, FALSE);
12992     SendToProgram("force\n", &first);
12993     if (startedFromSetupPosition) {
12994         SendBoard(&first, forwardMostMove);
12995     if (appData.debugMode) {
12996         fprintf(debugFP, "Load Game\n");
12997     }
12998         DisplayBothClocks();
12999     }
13000   }
13001
13002     /* [HGM] server: flag to write setup moves in broadcast file as one */
13003     loadFlag = appData.suppressLoadMoves;
13004
13005     while (cm == Comment) {
13006         char *p;
13007         if (appData.debugMode)
13008           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13009         p = yy_text;
13010         AppendComment(currentMove, p, FALSE);
13011         yyboardindex = forwardMostMove;
13012         cm = (ChessMove) Myylex();
13013     }
13014
13015     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13016         cm == WhiteWins || cm == BlackWins ||
13017         cm == GameIsDrawn || cm == GameUnfinished) {
13018         DisplayMessage("", _("No moves in game"));
13019         if (cmailMsgLoaded) {
13020             if (appData.debugMode)
13021               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13022             ClearHighlights();
13023             flipView = FALSE;
13024         }
13025         DrawPosition(FALSE, boards[currentMove]);
13026         DisplayBothClocks();
13027         gameMode = EditGame;
13028         ModeHighlight();
13029         gameFileFP = NULL;
13030         cmailOldMove = 0;
13031         return TRUE;
13032     }
13033
13034     // [HGM] PV info: routine tests if comment empty
13035     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13036         DisplayComment(currentMove - 1, commentList[currentMove]);
13037     }
13038     if (!matchMode && appData.timeDelay != 0)
13039       DrawPosition(FALSE, boards[currentMove]);
13040
13041     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13042       programStats.ok_to_send = 1;
13043     }
13044
13045     /* if the first token after the PGN tags is a move
13046      * and not move number 1, retrieve it from the parser
13047      */
13048     if (cm != MoveNumberOne)
13049         LoadGameOneMove(cm);
13050
13051     /* load the remaining moves from the file */
13052     while (LoadGameOneMove(EndOfFile)) {
13053       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13054       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13055     }
13056
13057     /* rewind to the start of the game */
13058     currentMove = backwardMostMove;
13059
13060     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13061
13062     if (oldGameMode == AnalyzeFile) {
13063       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13064       AnalyzeFileEvent();
13065     } else
13066     if (oldGameMode == AnalyzeMode) {
13067       AnalyzeFileEvent();
13068     }
13069
13070     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13071         long int w, b; // [HGM] adjourn: restore saved clock times
13072         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13073         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13074             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13075             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13076         }
13077     }
13078
13079     if(creatingBook) return TRUE;
13080     if (!matchMode && pos > 0) {
13081         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13082     } else
13083     if (matchMode || appData.timeDelay == 0) {
13084       ToEndEvent();
13085     } else if (appData.timeDelay > 0) {
13086       AutoPlayGameLoop();
13087     }
13088
13089     if (appData.debugMode)
13090         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13091
13092     loadFlag = 0; /* [HGM] true game starts */
13093     return TRUE;
13094 }
13095
13096 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13097 int
13098 ReloadPosition (int offset)
13099 {
13100     int positionNumber = lastLoadPositionNumber + offset;
13101     if (lastLoadPositionFP == NULL) {
13102         DisplayError(_("No position has been loaded yet"), 0);
13103         return FALSE;
13104     }
13105     if (positionNumber <= 0) {
13106         DisplayError(_("Can't back up any further"), 0);
13107         return FALSE;
13108     }
13109     return LoadPosition(lastLoadPositionFP, positionNumber,
13110                         lastLoadPositionTitle);
13111 }
13112
13113 /* Load the nth position from the given file */
13114 int
13115 LoadPositionFromFile (char *filename, int n, char *title)
13116 {
13117     FILE *f;
13118     char buf[MSG_SIZ];
13119
13120     if (strcmp(filename, "-") == 0) {
13121         return LoadPosition(stdin, n, "stdin");
13122     } else {
13123         f = fopen(filename, "rb");
13124         if (f == NULL) {
13125             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13126             DisplayError(buf, errno);
13127             return FALSE;
13128         } else {
13129             return LoadPosition(f, n, title);
13130         }
13131     }
13132 }
13133
13134 /* Load the nth position from the given open file, and close it */
13135 int
13136 LoadPosition (FILE *f, int positionNumber, char *title)
13137 {
13138     char *p, line[MSG_SIZ];
13139     Board initial_position;
13140     int i, j, fenMode, pn;
13141
13142     if (gameMode == Training )
13143         SetTrainingModeOff();
13144
13145     if (gameMode != BeginningOfGame) {
13146         Reset(FALSE, TRUE);
13147     }
13148     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13149         fclose(lastLoadPositionFP);
13150     }
13151     if (positionNumber == 0) positionNumber = 1;
13152     lastLoadPositionFP = f;
13153     lastLoadPositionNumber = positionNumber;
13154     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13155     if (first.pr == NoProc && !appData.noChessProgram) {
13156       StartChessProgram(&first);
13157       InitChessProgram(&first, FALSE);
13158     }
13159     pn = positionNumber;
13160     if (positionNumber < 0) {
13161         /* Negative position number means to seek to that byte offset */
13162         if (fseek(f, -positionNumber, 0) == -1) {
13163             DisplayError(_("Can't seek on position file"), 0);
13164             return FALSE;
13165         };
13166         pn = 1;
13167     } else {
13168         if (fseek(f, 0, 0) == -1) {
13169             if (f == lastLoadPositionFP ?
13170                 positionNumber == lastLoadPositionNumber + 1 :
13171                 positionNumber == 1) {
13172                 pn = 1;
13173             } else {
13174                 DisplayError(_("Can't seek on position file"), 0);
13175                 return FALSE;
13176             }
13177         }
13178     }
13179     /* See if this file is FEN or old-style xboard */
13180     if (fgets(line, MSG_SIZ, f) == NULL) {
13181         DisplayError(_("Position not found in file"), 0);
13182         return FALSE;
13183     }
13184     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13185     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13186
13187     if (pn >= 2) {
13188         if (fenMode || line[0] == '#') pn--;
13189         while (pn > 0) {
13190             /* skip positions before number pn */
13191             if (fgets(line, MSG_SIZ, f) == NULL) {
13192                 Reset(TRUE, TRUE);
13193                 DisplayError(_("Position not found in file"), 0);
13194                 return FALSE;
13195             }
13196             if (fenMode || line[0] == '#') pn--;
13197         }
13198     }
13199
13200     if (fenMode) {
13201         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13202             DisplayError(_("Bad FEN position in file"), 0);
13203             return FALSE;
13204         }
13205     } else {
13206         (void) fgets(line, MSG_SIZ, f);
13207         (void) fgets(line, MSG_SIZ, f);
13208
13209         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13210             (void) fgets(line, MSG_SIZ, f);
13211             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13212                 if (*p == ' ')
13213                   continue;
13214                 initial_position[i][j++] = CharToPiece(*p);
13215             }
13216         }
13217
13218         blackPlaysFirst = FALSE;
13219         if (!feof(f)) {
13220             (void) fgets(line, MSG_SIZ, f);
13221             if (strncmp(line, "black", strlen("black"))==0)
13222               blackPlaysFirst = TRUE;
13223         }
13224     }
13225     startedFromSetupPosition = TRUE;
13226
13227     CopyBoard(boards[0], initial_position);
13228     if (blackPlaysFirst) {
13229         currentMove = forwardMostMove = backwardMostMove = 1;
13230         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13231         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13232         CopyBoard(boards[1], initial_position);
13233         DisplayMessage("", _("Black to play"));
13234     } else {
13235         currentMove = forwardMostMove = backwardMostMove = 0;
13236         DisplayMessage("", _("White to play"));
13237     }
13238     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13239     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13240         SendToProgram("force\n", &first);
13241         SendBoard(&first, forwardMostMove);
13242     }
13243     if (appData.debugMode) {
13244 int i, j;
13245   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13246   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13247         fprintf(debugFP, "Load Position\n");
13248     }
13249
13250     if (positionNumber > 1) {
13251       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13252         DisplayTitle(line);
13253     } else {
13254         DisplayTitle(title);
13255     }
13256     gameMode = EditGame;
13257     ModeHighlight();
13258     ResetClocks();
13259     timeRemaining[0][1] = whiteTimeRemaining;
13260     timeRemaining[1][1] = blackTimeRemaining;
13261     DrawPosition(FALSE, boards[currentMove]);
13262
13263     return TRUE;
13264 }
13265
13266
13267 void
13268 CopyPlayerNameIntoFileName (char **dest, char *src)
13269 {
13270     while (*src != NULLCHAR && *src != ',') {
13271         if (*src == ' ') {
13272             *(*dest)++ = '_';
13273             src++;
13274         } else {
13275             *(*dest)++ = *src++;
13276         }
13277     }
13278 }
13279
13280 char *
13281 DefaultFileName (char *ext)
13282 {
13283     static char def[MSG_SIZ];
13284     char *p;
13285
13286     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13287         p = def;
13288         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13289         *p++ = '-';
13290         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13291         *p++ = '.';
13292         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13293     } else {
13294         def[0] = NULLCHAR;
13295     }
13296     return def;
13297 }
13298
13299 /* Save the current game to the given file */
13300 int
13301 SaveGameToFile (char *filename, int append)
13302 {
13303     FILE *f;
13304     char buf[MSG_SIZ];
13305     int result, i, t,tot=0;
13306
13307     if (strcmp(filename, "-") == 0) {
13308         return SaveGame(stdout, 0, NULL);
13309     } else {
13310         for(i=0; i<10; i++) { // upto 10 tries
13311              f = fopen(filename, append ? "a" : "w");
13312              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13313              if(f || errno != 13) break;
13314              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13315              tot += t;
13316         }
13317         if (f == NULL) {
13318             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13319             DisplayError(buf, errno);
13320             return FALSE;
13321         } else {
13322             safeStrCpy(buf, lastMsg, MSG_SIZ);
13323             DisplayMessage(_("Waiting for access to save file"), "");
13324             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13325             DisplayMessage(_("Saving game"), "");
13326             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13327             result = SaveGame(f, 0, NULL);
13328             DisplayMessage(buf, "");
13329             return result;
13330         }
13331     }
13332 }
13333
13334 char *
13335 SavePart (char *str)
13336 {
13337     static char buf[MSG_SIZ];
13338     char *p;
13339
13340     p = strchr(str, ' ');
13341     if (p == NULL) return str;
13342     strncpy(buf, str, p - str);
13343     buf[p - str] = NULLCHAR;
13344     return buf;
13345 }
13346
13347 #define PGN_MAX_LINE 75
13348
13349 #define PGN_SIDE_WHITE  0
13350 #define PGN_SIDE_BLACK  1
13351
13352 static int
13353 FindFirstMoveOutOfBook (int side)
13354 {
13355     int result = -1;
13356
13357     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13358         int index = backwardMostMove;
13359         int has_book_hit = 0;
13360
13361         if( (index % 2) != side ) {
13362             index++;
13363         }
13364
13365         while( index < forwardMostMove ) {
13366             /* Check to see if engine is in book */
13367             int depth = pvInfoList[index].depth;
13368             int score = pvInfoList[index].score;
13369             int in_book = 0;
13370
13371             if( depth <= 2 ) {
13372                 in_book = 1;
13373             }
13374             else if( score == 0 && depth == 63 ) {
13375                 in_book = 1; /* Zappa */
13376             }
13377             else if( score == 2 && depth == 99 ) {
13378                 in_book = 1; /* Abrok */
13379             }
13380
13381             has_book_hit += in_book;
13382
13383             if( ! in_book ) {
13384                 result = index;
13385
13386                 break;
13387             }
13388
13389             index += 2;
13390         }
13391     }
13392
13393     return result;
13394 }
13395
13396 void
13397 GetOutOfBookInfo (char * buf)
13398 {
13399     int oob[2];
13400     int i;
13401     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13402
13403     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13404     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13405
13406     *buf = '\0';
13407
13408     if( oob[0] >= 0 || oob[1] >= 0 ) {
13409         for( i=0; i<2; i++ ) {
13410             int idx = oob[i];
13411
13412             if( idx >= 0 ) {
13413                 if( i > 0 && oob[0] >= 0 ) {
13414                     strcat( buf, "   " );
13415                 }
13416
13417                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13418                 sprintf( buf+strlen(buf), "%s%.2f",
13419                     pvInfoList[idx].score >= 0 ? "+" : "",
13420                     pvInfoList[idx].score / 100.0 );
13421             }
13422         }
13423     }
13424 }
13425
13426 /* Save game in PGN style */
13427 static void
13428 SaveGamePGN2 (FILE *f)
13429 {
13430     int i, offset, linelen, newblock;
13431 //    char *movetext;
13432     char numtext[32];
13433     int movelen, numlen, blank;
13434     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13435
13436     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13437
13438     PrintPGNTags(f, &gameInfo);
13439
13440     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13441
13442     if (backwardMostMove > 0 || startedFromSetupPosition) {
13443         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13444         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13445         fprintf(f, "\n{--------------\n");
13446         PrintPosition(f, backwardMostMove);
13447         fprintf(f, "--------------}\n");
13448         free(fen);
13449     }
13450     else {
13451         /* [AS] Out of book annotation */
13452         if( appData.saveOutOfBookInfo ) {
13453             char buf[64];
13454
13455             GetOutOfBookInfo( buf );
13456
13457             if( buf[0] != '\0' ) {
13458                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13459             }
13460         }
13461
13462         fprintf(f, "\n");
13463     }
13464
13465     i = backwardMostMove;
13466     linelen = 0;
13467     newblock = TRUE;
13468
13469     while (i < forwardMostMove) {
13470         /* Print comments preceding this move */
13471         if (commentList[i] != NULL) {
13472             if (linelen > 0) fprintf(f, "\n");
13473             fprintf(f, "%s", commentList[i]);
13474             linelen = 0;
13475             newblock = TRUE;
13476         }
13477
13478         /* Format move number */
13479         if ((i % 2) == 0)
13480           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13481         else
13482           if (newblock)
13483             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13484           else
13485             numtext[0] = NULLCHAR;
13486
13487         numlen = strlen(numtext);
13488         newblock = FALSE;
13489
13490         /* Print move number */
13491         blank = linelen > 0 && numlen > 0;
13492         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13493             fprintf(f, "\n");
13494             linelen = 0;
13495             blank = 0;
13496         }
13497         if (blank) {
13498             fprintf(f, " ");
13499             linelen++;
13500         }
13501         fprintf(f, "%s", numtext);
13502         linelen += numlen;
13503
13504         /* Get move */
13505         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13506         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13507
13508         /* Print move */
13509         blank = linelen > 0 && movelen > 0;
13510         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13511             fprintf(f, "\n");
13512             linelen = 0;
13513             blank = 0;
13514         }
13515         if (blank) {
13516             fprintf(f, " ");
13517             linelen++;
13518         }
13519         fprintf(f, "%s", move_buffer);
13520         linelen += movelen;
13521
13522         /* [AS] Add PV info if present */
13523         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13524             /* [HGM] add time */
13525             char buf[MSG_SIZ]; int seconds;
13526
13527             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13528
13529             if( seconds <= 0)
13530               buf[0] = 0;
13531             else
13532               if( seconds < 30 )
13533                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13534               else
13535                 {
13536                   seconds = (seconds + 4)/10; // round to full seconds
13537                   if( seconds < 60 )
13538                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13539                   else
13540                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13541                 }
13542
13543             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13544                       pvInfoList[i].score >= 0 ? "+" : "",
13545                       pvInfoList[i].score / 100.0,
13546                       pvInfoList[i].depth,
13547                       buf );
13548
13549             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13550
13551             /* Print score/depth */
13552             blank = linelen > 0 && movelen > 0;
13553             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13554                 fprintf(f, "\n");
13555                 linelen = 0;
13556                 blank = 0;
13557             }
13558             if (blank) {
13559                 fprintf(f, " ");
13560                 linelen++;
13561             }
13562             fprintf(f, "%s", move_buffer);
13563             linelen += movelen;
13564         }
13565
13566         i++;
13567     }
13568
13569     /* Start a new line */
13570     if (linelen > 0) fprintf(f, "\n");
13571
13572     /* Print comments after last move */
13573     if (commentList[i] != NULL) {
13574         fprintf(f, "%s\n", commentList[i]);
13575     }
13576
13577     /* Print result */
13578     if (gameInfo.resultDetails != NULL &&
13579         gameInfo.resultDetails[0] != NULLCHAR) {
13580         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13581         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13582            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13583             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13584         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13585     } else {
13586         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13587     }
13588 }
13589
13590 /* Save game in PGN style and close the file */
13591 int
13592 SaveGamePGN (FILE *f)
13593 {
13594     SaveGamePGN2(f);
13595     fclose(f);
13596     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13597     return TRUE;
13598 }
13599
13600 /* Save game in old style and close the file */
13601 int
13602 SaveGameOldStyle (FILE *f)
13603 {
13604     int i, offset;
13605     time_t tm;
13606
13607     tm = time((time_t *) NULL);
13608
13609     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13610     PrintOpponents(f);
13611
13612     if (backwardMostMove > 0 || startedFromSetupPosition) {
13613         fprintf(f, "\n[--------------\n");
13614         PrintPosition(f, backwardMostMove);
13615         fprintf(f, "--------------]\n");
13616     } else {
13617         fprintf(f, "\n");
13618     }
13619
13620     i = backwardMostMove;
13621     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13622
13623     while (i < forwardMostMove) {
13624         if (commentList[i] != NULL) {
13625             fprintf(f, "[%s]\n", commentList[i]);
13626         }
13627
13628         if ((i % 2) == 1) {
13629             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13630             i++;
13631         } else {
13632             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13633             i++;
13634             if (commentList[i] != NULL) {
13635                 fprintf(f, "\n");
13636                 continue;
13637             }
13638             if (i >= forwardMostMove) {
13639                 fprintf(f, "\n");
13640                 break;
13641             }
13642             fprintf(f, "%s\n", parseList[i]);
13643             i++;
13644         }
13645     }
13646
13647     if (commentList[i] != NULL) {
13648         fprintf(f, "[%s]\n", commentList[i]);
13649     }
13650
13651     /* This isn't really the old style, but it's close enough */
13652     if (gameInfo.resultDetails != NULL &&
13653         gameInfo.resultDetails[0] != NULLCHAR) {
13654         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13655                 gameInfo.resultDetails);
13656     } else {
13657         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13658     }
13659
13660     fclose(f);
13661     return TRUE;
13662 }
13663
13664 /* Save the current game to open file f and close the file */
13665 int
13666 SaveGame (FILE *f, int dummy, char *dummy2)
13667 {
13668     if (gameMode == EditPosition) EditPositionDone(TRUE);
13669     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13670     if (appData.oldSaveStyle)
13671       return SaveGameOldStyle(f);
13672     else
13673       return SaveGamePGN(f);
13674 }
13675
13676 /* Save the current position to the given file */
13677 int
13678 SavePositionToFile (char *filename)
13679 {
13680     FILE *f;
13681     char buf[MSG_SIZ];
13682
13683     if (strcmp(filename, "-") == 0) {
13684         return SavePosition(stdout, 0, NULL);
13685     } else {
13686         f = fopen(filename, "a");
13687         if (f == NULL) {
13688             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13689             DisplayError(buf, errno);
13690             return FALSE;
13691         } else {
13692             safeStrCpy(buf, lastMsg, MSG_SIZ);
13693             DisplayMessage(_("Waiting for access to save file"), "");
13694             flock(fileno(f), LOCK_EX); // [HGM] lock
13695             DisplayMessage(_("Saving position"), "");
13696             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13697             SavePosition(f, 0, NULL);
13698             DisplayMessage(buf, "");
13699             return TRUE;
13700         }
13701     }
13702 }
13703
13704 /* Save the current position to the given open file and close the file */
13705 int
13706 SavePosition (FILE *f, int dummy, char *dummy2)
13707 {
13708     time_t tm;
13709     char *fen;
13710
13711     if (gameMode == EditPosition) EditPositionDone(TRUE);
13712     if (appData.oldSaveStyle) {
13713         tm = time((time_t *) NULL);
13714
13715         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13716         PrintOpponents(f);
13717         fprintf(f, "[--------------\n");
13718         PrintPosition(f, currentMove);
13719         fprintf(f, "--------------]\n");
13720     } else {
13721         fen = PositionToFEN(currentMove, NULL, 1);
13722         fprintf(f, "%s\n", fen);
13723         free(fen);
13724     }
13725     fclose(f);
13726     return TRUE;
13727 }
13728
13729 void
13730 ReloadCmailMsgEvent (int unregister)
13731 {
13732 #if !WIN32
13733     static char *inFilename = NULL;
13734     static char *outFilename;
13735     int i;
13736     struct stat inbuf, outbuf;
13737     int status;
13738
13739     /* Any registered moves are unregistered if unregister is set, */
13740     /* i.e. invoked by the signal handler */
13741     if (unregister) {
13742         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13743             cmailMoveRegistered[i] = FALSE;
13744             if (cmailCommentList[i] != NULL) {
13745                 free(cmailCommentList[i]);
13746                 cmailCommentList[i] = NULL;
13747             }
13748         }
13749         nCmailMovesRegistered = 0;
13750     }
13751
13752     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13753         cmailResult[i] = CMAIL_NOT_RESULT;
13754     }
13755     nCmailResults = 0;
13756
13757     if (inFilename == NULL) {
13758         /* Because the filenames are static they only get malloced once  */
13759         /* and they never get freed                                      */
13760         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13761         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13762
13763         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13764         sprintf(outFilename, "%s.out", appData.cmailGameName);
13765     }
13766
13767     status = stat(outFilename, &outbuf);
13768     if (status < 0) {
13769         cmailMailedMove = FALSE;
13770     } else {
13771         status = stat(inFilename, &inbuf);
13772         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13773     }
13774
13775     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13776        counts the games, notes how each one terminated, etc.
13777
13778        It would be nice to remove this kludge and instead gather all
13779        the information while building the game list.  (And to keep it
13780        in the game list nodes instead of having a bunch of fixed-size
13781        parallel arrays.)  Note this will require getting each game's
13782        termination from the PGN tags, as the game list builder does
13783        not process the game moves.  --mann
13784        */
13785     cmailMsgLoaded = TRUE;
13786     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13787
13788     /* Load first game in the file or popup game menu */
13789     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13790
13791 #endif /* !WIN32 */
13792     return;
13793 }
13794
13795 int
13796 RegisterMove ()
13797 {
13798     FILE *f;
13799     char string[MSG_SIZ];
13800
13801     if (   cmailMailedMove
13802         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13803         return TRUE;            /* Allow free viewing  */
13804     }
13805
13806     /* Unregister move to ensure that we don't leave RegisterMove        */
13807     /* with the move registered when the conditions for registering no   */
13808     /* longer hold                                                       */
13809     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13810         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13811         nCmailMovesRegistered --;
13812
13813         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13814           {
13815               free(cmailCommentList[lastLoadGameNumber - 1]);
13816               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13817           }
13818     }
13819
13820     if (cmailOldMove == -1) {
13821         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13822         return FALSE;
13823     }
13824
13825     if (currentMove > cmailOldMove + 1) {
13826         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13827         return FALSE;
13828     }
13829
13830     if (currentMove < cmailOldMove) {
13831         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13832         return FALSE;
13833     }
13834
13835     if (forwardMostMove > currentMove) {
13836         /* Silently truncate extra moves */
13837         TruncateGame();
13838     }
13839
13840     if (   (currentMove == cmailOldMove + 1)
13841         || (   (currentMove == cmailOldMove)
13842             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13843                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13844         if (gameInfo.result != GameUnfinished) {
13845             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13846         }
13847
13848         if (commentList[currentMove] != NULL) {
13849             cmailCommentList[lastLoadGameNumber - 1]
13850               = StrSave(commentList[currentMove]);
13851         }
13852         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13853
13854         if (appData.debugMode)
13855           fprintf(debugFP, "Saving %s for game %d\n",
13856                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13857
13858         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13859
13860         f = fopen(string, "w");
13861         if (appData.oldSaveStyle) {
13862             SaveGameOldStyle(f); /* also closes the file */
13863
13864             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13865             f = fopen(string, "w");
13866             SavePosition(f, 0, NULL); /* also closes the file */
13867         } else {
13868             fprintf(f, "{--------------\n");
13869             PrintPosition(f, currentMove);
13870             fprintf(f, "--------------}\n\n");
13871
13872             SaveGame(f, 0, NULL); /* also closes the file*/
13873         }
13874
13875         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13876         nCmailMovesRegistered ++;
13877     } else if (nCmailGames == 1) {
13878         DisplayError(_("You have not made a move yet"), 0);
13879         return FALSE;
13880     }
13881
13882     return TRUE;
13883 }
13884
13885 void
13886 MailMoveEvent ()
13887 {
13888 #if !WIN32
13889     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13890     FILE *commandOutput;
13891     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13892     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13893     int nBuffers;
13894     int i;
13895     int archived;
13896     char *arcDir;
13897
13898     if (! cmailMsgLoaded) {
13899         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13900         return;
13901     }
13902
13903     if (nCmailGames == nCmailResults) {
13904         DisplayError(_("No unfinished games"), 0);
13905         return;
13906     }
13907
13908 #if CMAIL_PROHIBIT_REMAIL
13909     if (cmailMailedMove) {
13910       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);
13911         DisplayError(msg, 0);
13912         return;
13913     }
13914 #endif
13915
13916     if (! (cmailMailedMove || RegisterMove())) return;
13917
13918     if (   cmailMailedMove
13919         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13920       snprintf(string, MSG_SIZ, partCommandString,
13921                appData.debugMode ? " -v" : "", appData.cmailGameName);
13922         commandOutput = popen(string, "r");
13923
13924         if (commandOutput == NULL) {
13925             DisplayError(_("Failed to invoke cmail"), 0);
13926         } else {
13927             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13928                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13929             }
13930             if (nBuffers > 1) {
13931                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13932                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13933                 nBytes = MSG_SIZ - 1;
13934             } else {
13935                 (void) memcpy(msg, buffer, nBytes);
13936             }
13937             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13938
13939             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13940                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13941
13942                 archived = TRUE;
13943                 for (i = 0; i < nCmailGames; i ++) {
13944                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13945                         archived = FALSE;
13946                     }
13947                 }
13948                 if (   archived
13949                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13950                         != NULL)) {
13951                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13952                            arcDir,
13953                            appData.cmailGameName,
13954                            gameInfo.date);
13955                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13956                     cmailMsgLoaded = FALSE;
13957                 }
13958             }
13959
13960             DisplayInformation(msg);
13961             pclose(commandOutput);
13962         }
13963     } else {
13964         if ((*cmailMsg) != '\0') {
13965             DisplayInformation(cmailMsg);
13966         }
13967     }
13968
13969     return;
13970 #endif /* !WIN32 */
13971 }
13972
13973 char *
13974 CmailMsg ()
13975 {
13976 #if WIN32
13977     return NULL;
13978 #else
13979     int  prependComma = 0;
13980     char number[5];
13981     char string[MSG_SIZ];       /* Space for game-list */
13982     int  i;
13983
13984     if (!cmailMsgLoaded) return "";
13985
13986     if (cmailMailedMove) {
13987       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13988     } else {
13989         /* Create a list of games left */
13990       snprintf(string, MSG_SIZ, "[");
13991         for (i = 0; i < nCmailGames; i ++) {
13992             if (! (   cmailMoveRegistered[i]
13993                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13994                 if (prependComma) {
13995                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13996                 } else {
13997                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13998                     prependComma = 1;
13999                 }
14000
14001                 strcat(string, number);
14002             }
14003         }
14004         strcat(string, "]");
14005
14006         if (nCmailMovesRegistered + nCmailResults == 0) {
14007             switch (nCmailGames) {
14008               case 1:
14009                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14010                 break;
14011
14012               case 2:
14013                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14014                 break;
14015
14016               default:
14017                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14018                          nCmailGames);
14019                 break;
14020             }
14021         } else {
14022             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14023               case 1:
14024                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14025                          string);
14026                 break;
14027
14028               case 0:
14029                 if (nCmailResults == nCmailGames) {
14030                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14031                 } else {
14032                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14033                 }
14034                 break;
14035
14036               default:
14037                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14038                          string);
14039             }
14040         }
14041     }
14042     return cmailMsg;
14043 #endif /* WIN32 */
14044 }
14045
14046 void
14047 ResetGameEvent ()
14048 {
14049     if (gameMode == Training)
14050       SetTrainingModeOff();
14051
14052     Reset(TRUE, TRUE);
14053     cmailMsgLoaded = FALSE;
14054     if (appData.icsActive) {
14055       SendToICS(ics_prefix);
14056       SendToICS("refresh\n");
14057     }
14058 }
14059
14060 void
14061 ExitEvent (int status)
14062 {
14063     exiting++;
14064     if (exiting > 2) {
14065       /* Give up on clean exit */
14066       exit(status);
14067     }
14068     if (exiting > 1) {
14069       /* Keep trying for clean exit */
14070       return;
14071     }
14072
14073     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14074     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14075
14076     if (telnetISR != NULL) {
14077       RemoveInputSource(telnetISR);
14078     }
14079     if (icsPR != NoProc) {
14080       DestroyChildProcess(icsPR, TRUE);
14081     }
14082
14083     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14084     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14085
14086     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14087     /* make sure this other one finishes before killing it!                  */
14088     if(endingGame) { int count = 0;
14089         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14090         while(endingGame && count++ < 10) DoSleep(1);
14091         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14092     }
14093
14094     /* Kill off chess programs */
14095     if (first.pr != NoProc) {
14096         ExitAnalyzeMode();
14097
14098         DoSleep( appData.delayBeforeQuit );
14099         SendToProgram("quit\n", &first);
14100         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14101     }
14102     if (second.pr != NoProc) {
14103         DoSleep( appData.delayBeforeQuit );
14104         SendToProgram("quit\n", &second);
14105         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14106     }
14107     if (first.isr != NULL) {
14108         RemoveInputSource(first.isr);
14109     }
14110     if (second.isr != NULL) {
14111         RemoveInputSource(second.isr);
14112     }
14113
14114     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14115     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14116
14117     ShutDownFrontEnd();
14118     exit(status);
14119 }
14120
14121 void
14122 PauseEngine (ChessProgramState *cps)
14123 {
14124     SendToProgram("pause\n", cps);
14125     cps->pause = 2;
14126 }
14127
14128 void
14129 UnPauseEngine (ChessProgramState *cps)
14130 {
14131     SendToProgram("resume\n", cps);
14132     cps->pause = 1;
14133 }
14134
14135 void
14136 PauseEvent ()
14137 {
14138     if (appData.debugMode)
14139         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14140     if (pausing) {
14141         pausing = FALSE;
14142         ModeHighlight();
14143         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14144             StartClocks();
14145             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14146                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14147                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14148             }
14149             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14150             HandleMachineMove(stashedInputMove, stalledEngine);
14151             stalledEngine = NULL;
14152             return;
14153         }
14154         if (gameMode == MachinePlaysWhite ||
14155             gameMode == TwoMachinesPlay   ||
14156             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14157             if(first.pause)  UnPauseEngine(&first);
14158             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14159             if(second.pause) UnPauseEngine(&second);
14160             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14161             StartClocks();
14162         } else {
14163             DisplayBothClocks();
14164         }
14165         if (gameMode == PlayFromGameFile) {
14166             if (appData.timeDelay >= 0)
14167                 AutoPlayGameLoop();
14168         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14169             Reset(FALSE, TRUE);
14170             SendToICS(ics_prefix);
14171             SendToICS("refresh\n");
14172         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14173             ForwardInner(forwardMostMove);
14174         }
14175         pauseExamInvalid = FALSE;
14176     } else {
14177         switch (gameMode) {
14178           default:
14179             return;
14180           case IcsExamining:
14181             pauseExamForwardMostMove = forwardMostMove;
14182             pauseExamInvalid = FALSE;
14183             /* fall through */
14184           case IcsObserving:
14185           case IcsPlayingWhite:
14186           case IcsPlayingBlack:
14187             pausing = TRUE;
14188             ModeHighlight();
14189             return;
14190           case PlayFromGameFile:
14191             (void) StopLoadGameTimer();
14192             pausing = TRUE;
14193             ModeHighlight();
14194             break;
14195           case BeginningOfGame:
14196             if (appData.icsActive) return;
14197             /* else fall through */
14198           case MachinePlaysWhite:
14199           case MachinePlaysBlack:
14200           case TwoMachinesPlay:
14201             if (forwardMostMove == 0)
14202               return;           /* don't pause if no one has moved */
14203             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14204                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14205                 if(onMove->pause) {           // thinking engine can be paused
14206                     PauseEngine(onMove);      // do it
14207                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14208                         PauseEngine(onMove->other);
14209                     else
14210                         SendToProgram("easy\n", onMove->other);
14211                     StopClocks();
14212                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14213             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14214                 if(first.pause) {
14215                     PauseEngine(&first);
14216                     StopClocks();
14217                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14218             } else { // human on move, pause pondering by either method
14219                 if(first.pause)
14220                     PauseEngine(&first);
14221                 else if(appData.ponderNextMove)
14222                     SendToProgram("easy\n", &first);
14223                 StopClocks();
14224             }
14225             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14226           case AnalyzeMode:
14227             pausing = TRUE;
14228             ModeHighlight();
14229             break;
14230         }
14231     }
14232 }
14233
14234 void
14235 EditCommentEvent ()
14236 {
14237     char title[MSG_SIZ];
14238
14239     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14240       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14241     } else {
14242       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14243                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14244                parseList[currentMove - 1]);
14245     }
14246
14247     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14248 }
14249
14250
14251 void
14252 EditTagsEvent ()
14253 {
14254     char *tags = PGNTags(&gameInfo);
14255     bookUp = FALSE;
14256     EditTagsPopUp(tags, NULL);
14257     free(tags);
14258 }
14259
14260 void
14261 ToggleSecond ()
14262 {
14263   if(second.analyzing) {
14264     SendToProgram("exit\n", &second);
14265     second.analyzing = FALSE;
14266   } else {
14267     if (second.pr == NoProc) StartChessProgram(&second);
14268     InitChessProgram(&second, FALSE);
14269     FeedMovesToProgram(&second, currentMove);
14270
14271     SendToProgram("analyze\n", &second);
14272     second.analyzing = TRUE;
14273   }
14274 }
14275
14276 /* Toggle ShowThinking */
14277 void
14278 ToggleShowThinking()
14279 {
14280   appData.showThinking = !appData.showThinking;
14281   ShowThinkingEvent();
14282 }
14283
14284 int
14285 AnalyzeModeEvent ()
14286 {
14287     char buf[MSG_SIZ];
14288
14289     if (!first.analysisSupport) {
14290       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14291       DisplayError(buf, 0);
14292       return 0;
14293     }
14294     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14295     if (appData.icsActive) {
14296         if (gameMode != IcsObserving) {
14297           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14298             DisplayError(buf, 0);
14299             /* secure check */
14300             if (appData.icsEngineAnalyze) {
14301                 if (appData.debugMode)
14302                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14303                 ExitAnalyzeMode();
14304                 ModeHighlight();
14305             }
14306             return 0;
14307         }
14308         /* if enable, user wants to disable icsEngineAnalyze */
14309         if (appData.icsEngineAnalyze) {
14310                 ExitAnalyzeMode();
14311                 ModeHighlight();
14312                 return 0;
14313         }
14314         appData.icsEngineAnalyze = TRUE;
14315         if (appData.debugMode)
14316             fprintf(debugFP, "ICS engine analyze starting... \n");
14317     }
14318
14319     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14320     if (appData.noChessProgram || gameMode == AnalyzeMode)
14321       return 0;
14322
14323     if (gameMode != AnalyzeFile) {
14324         if (!appData.icsEngineAnalyze) {
14325                EditGameEvent();
14326                if (gameMode != EditGame) return 0;
14327         }
14328         if (!appData.showThinking) ToggleShowThinking();
14329         ResurrectChessProgram();
14330         SendToProgram("analyze\n", &first);
14331         first.analyzing = TRUE;
14332         /*first.maybeThinking = TRUE;*/
14333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14334         EngineOutputPopUp();
14335     }
14336     if (!appData.icsEngineAnalyze) {
14337         gameMode = AnalyzeMode;
14338         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14339     }
14340     pausing = FALSE;
14341     ModeHighlight();
14342     SetGameInfo();
14343
14344     StartAnalysisClock();
14345     GetTimeMark(&lastNodeCountTime);
14346     lastNodeCount = 0;
14347     return 1;
14348 }
14349
14350 void
14351 AnalyzeFileEvent ()
14352 {
14353     if (appData.noChessProgram || gameMode == AnalyzeFile)
14354       return;
14355
14356     if (!first.analysisSupport) {
14357       char buf[MSG_SIZ];
14358       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14359       DisplayError(buf, 0);
14360       return;
14361     }
14362
14363     if (gameMode != AnalyzeMode) {
14364         keepInfo = 1; // mere annotating should not alter PGN tags
14365         EditGameEvent();
14366         keepInfo = 0;
14367         if (gameMode != EditGame) return;
14368         if (!appData.showThinking) ToggleShowThinking();
14369         ResurrectChessProgram();
14370         SendToProgram("analyze\n", &first);
14371         first.analyzing = TRUE;
14372         /*first.maybeThinking = TRUE;*/
14373         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14374         EngineOutputPopUp();
14375     }
14376     gameMode = AnalyzeFile;
14377     pausing = FALSE;
14378     ModeHighlight();
14379
14380     StartAnalysisClock();
14381     GetTimeMark(&lastNodeCountTime);
14382     lastNodeCount = 0;
14383     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14384     AnalysisPeriodicEvent(1);
14385 }
14386
14387 void
14388 MachineWhiteEvent ()
14389 {
14390     char buf[MSG_SIZ];
14391     char *bookHit = NULL;
14392
14393     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14394       return;
14395
14396
14397     if (gameMode == PlayFromGameFile ||
14398         gameMode == TwoMachinesPlay  ||
14399         gameMode == Training         ||
14400         gameMode == AnalyzeMode      ||
14401         gameMode == EndOfGame)
14402         EditGameEvent();
14403
14404     if (gameMode == EditPosition)
14405         EditPositionDone(TRUE);
14406
14407     if (!WhiteOnMove(currentMove)) {
14408         DisplayError(_("It is not White's turn"), 0);
14409         return;
14410     }
14411
14412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14413       ExitAnalyzeMode();
14414
14415     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14416         gameMode == AnalyzeFile)
14417         TruncateGame();
14418
14419     ResurrectChessProgram();    /* in case it isn't running */
14420     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14421         gameMode = MachinePlaysWhite;
14422         ResetClocks();
14423     } else
14424     gameMode = MachinePlaysWhite;
14425     pausing = FALSE;
14426     ModeHighlight();
14427     SetGameInfo();
14428     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14429     DisplayTitle(buf);
14430     if (first.sendName) {
14431       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14432       SendToProgram(buf, &first);
14433     }
14434     if (first.sendTime) {
14435       if (first.useColors) {
14436         SendToProgram("black\n", &first); /*gnu kludge*/
14437       }
14438       SendTimeRemaining(&first, TRUE);
14439     }
14440     if (first.useColors) {
14441       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14442     }
14443     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14444     SetMachineThinkingEnables();
14445     first.maybeThinking = TRUE;
14446     StartClocks();
14447     firstMove = FALSE;
14448
14449     if (appData.autoFlipView && !flipView) {
14450       flipView = !flipView;
14451       DrawPosition(FALSE, NULL);
14452       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14453     }
14454
14455     if(bookHit) { // [HGM] book: simulate book reply
14456         static char bookMove[MSG_SIZ]; // a bit generous?
14457
14458         programStats.nodes = programStats.depth = programStats.time =
14459         programStats.score = programStats.got_only_move = 0;
14460         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14461
14462         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14463         strcat(bookMove, bookHit);
14464         HandleMachineMove(bookMove, &first);
14465     }
14466 }
14467
14468 void
14469 MachineBlackEvent ()
14470 {
14471   char buf[MSG_SIZ];
14472   char *bookHit = NULL;
14473
14474     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14475         return;
14476
14477
14478     if (gameMode == PlayFromGameFile ||
14479         gameMode == TwoMachinesPlay  ||
14480         gameMode == Training         ||
14481         gameMode == AnalyzeMode      ||
14482         gameMode == EndOfGame)
14483         EditGameEvent();
14484
14485     if (gameMode == EditPosition)
14486         EditPositionDone(TRUE);
14487
14488     if (WhiteOnMove(currentMove)) {
14489         DisplayError(_("It is not Black's turn"), 0);
14490         return;
14491     }
14492
14493     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14494       ExitAnalyzeMode();
14495
14496     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14497         gameMode == AnalyzeFile)
14498         TruncateGame();
14499
14500     ResurrectChessProgram();    /* in case it isn't running */
14501     gameMode = MachinePlaysBlack;
14502     pausing = FALSE;
14503     ModeHighlight();
14504     SetGameInfo();
14505     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14506     DisplayTitle(buf);
14507     if (first.sendName) {
14508       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14509       SendToProgram(buf, &first);
14510     }
14511     if (first.sendTime) {
14512       if (first.useColors) {
14513         SendToProgram("white\n", &first); /*gnu kludge*/
14514       }
14515       SendTimeRemaining(&first, FALSE);
14516     }
14517     if (first.useColors) {
14518       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14519     }
14520     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14521     SetMachineThinkingEnables();
14522     first.maybeThinking = TRUE;
14523     StartClocks();
14524
14525     if (appData.autoFlipView && flipView) {
14526       flipView = !flipView;
14527       DrawPosition(FALSE, NULL);
14528       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14529     }
14530     if(bookHit) { // [HGM] book: simulate book reply
14531         static char bookMove[MSG_SIZ]; // a bit generous?
14532
14533         programStats.nodes = programStats.depth = programStats.time =
14534         programStats.score = programStats.got_only_move = 0;
14535         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14536
14537         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14538         strcat(bookMove, bookHit);
14539         HandleMachineMove(bookMove, &first);
14540     }
14541 }
14542
14543
14544 void
14545 DisplayTwoMachinesTitle ()
14546 {
14547     char buf[MSG_SIZ];
14548     if (appData.matchGames > 0) {
14549         if(appData.tourneyFile[0]) {
14550           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14551                    gameInfo.white, _("vs."), gameInfo.black,
14552                    nextGame+1, appData.matchGames+1,
14553                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14554         } else
14555         if (first.twoMachinesColor[0] == 'w') {
14556           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14557                    gameInfo.white, _("vs."),  gameInfo.black,
14558                    first.matchWins, second.matchWins,
14559                    matchGame - 1 - (first.matchWins + second.matchWins));
14560         } else {
14561           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14562                    gameInfo.white, _("vs."), gameInfo.black,
14563                    second.matchWins, first.matchWins,
14564                    matchGame - 1 - (first.matchWins + second.matchWins));
14565         }
14566     } else {
14567       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14568     }
14569     DisplayTitle(buf);
14570 }
14571
14572 void
14573 SettingsMenuIfReady ()
14574 {
14575   if (second.lastPing != second.lastPong) {
14576     DisplayMessage("", _("Waiting for second chess program"));
14577     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14578     return;
14579   }
14580   ThawUI();
14581   DisplayMessage("", "");
14582   SettingsPopUp(&second);
14583 }
14584
14585 int
14586 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14587 {
14588     char buf[MSG_SIZ];
14589     if (cps->pr == NoProc) {
14590         StartChessProgram(cps);
14591         if (cps->protocolVersion == 1) {
14592           retry();
14593           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14594         } else {
14595           /* kludge: allow timeout for initial "feature" command */
14596           if(retry != TwoMachinesEventIfReady) FreezeUI();
14597           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14598           DisplayMessage("", buf);
14599           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14600         }
14601         return 1;
14602     }
14603     return 0;
14604 }
14605
14606 void
14607 TwoMachinesEvent P((void))
14608 {
14609     int i;
14610     char buf[MSG_SIZ];
14611     ChessProgramState *onmove;
14612     char *bookHit = NULL;
14613     static int stalling = 0;
14614     TimeMark now;
14615     long wait;
14616
14617     if (appData.noChessProgram) return;
14618
14619     switch (gameMode) {
14620       case TwoMachinesPlay:
14621         return;
14622       case MachinePlaysWhite:
14623       case MachinePlaysBlack:
14624         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14625             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14626             return;
14627         }
14628         /* fall through */
14629       case BeginningOfGame:
14630       case PlayFromGameFile:
14631       case EndOfGame:
14632         EditGameEvent();
14633         if (gameMode != EditGame) return;
14634         break;
14635       case EditPosition:
14636         EditPositionDone(TRUE);
14637         break;
14638       case AnalyzeMode:
14639       case AnalyzeFile:
14640         ExitAnalyzeMode();
14641         break;
14642       case EditGame:
14643       default:
14644         break;
14645     }
14646
14647 //    forwardMostMove = currentMove;
14648     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14649     startingEngine = TRUE;
14650
14651     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14652
14653     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14654     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14655       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14656       return;
14657     }
14658     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14659
14660     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14661                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14662         startingEngine = matchMode = FALSE;
14663         DisplayError("second engine does not play this", 0);
14664         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14665         EditGameEvent(); // switch back to EditGame mode
14666         return;
14667     }
14668
14669     if(!stalling) {
14670       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14671       SendToProgram("force\n", &second);
14672       stalling = 1;
14673       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14674       return;
14675     }
14676     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14677     if(appData.matchPause>10000 || appData.matchPause<10)
14678                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14679     wait = SubtractTimeMarks(&now, &pauseStart);
14680     if(wait < appData.matchPause) {
14681         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14682         return;
14683     }
14684     // we are now committed to starting the game
14685     stalling = 0;
14686     DisplayMessage("", "");
14687     if (startedFromSetupPosition) {
14688         SendBoard(&second, backwardMostMove);
14689     if (appData.debugMode) {
14690         fprintf(debugFP, "Two Machines\n");
14691     }
14692     }
14693     for (i = backwardMostMove; i < forwardMostMove; i++) {
14694         SendMoveToProgram(i, &second);
14695     }
14696
14697     gameMode = TwoMachinesPlay;
14698     pausing = startingEngine = FALSE;
14699     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14700     SetGameInfo();
14701     DisplayTwoMachinesTitle();
14702     firstMove = TRUE;
14703     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14704         onmove = &first;
14705     } else {
14706         onmove = &second;
14707     }
14708     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14709     SendToProgram(first.computerString, &first);
14710     if (first.sendName) {
14711       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14712       SendToProgram(buf, &first);
14713     }
14714     SendToProgram(second.computerString, &second);
14715     if (second.sendName) {
14716       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14717       SendToProgram(buf, &second);
14718     }
14719
14720     ResetClocks();
14721     if (!first.sendTime || !second.sendTime) {
14722         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14723         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14724     }
14725     if (onmove->sendTime) {
14726       if (onmove->useColors) {
14727         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14728       }
14729       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14730     }
14731     if (onmove->useColors) {
14732       SendToProgram(onmove->twoMachinesColor, onmove);
14733     }
14734     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14735 //    SendToProgram("go\n", onmove);
14736     onmove->maybeThinking = TRUE;
14737     SetMachineThinkingEnables();
14738
14739     StartClocks();
14740
14741     if(bookHit) { // [HGM] book: simulate book reply
14742         static char bookMove[MSG_SIZ]; // a bit generous?
14743
14744         programStats.nodes = programStats.depth = programStats.time =
14745         programStats.score = programStats.got_only_move = 0;
14746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14747
14748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14749         strcat(bookMove, bookHit);
14750         savedMessage = bookMove; // args for deferred call
14751         savedState = onmove;
14752         ScheduleDelayedEvent(DeferredBookMove, 1);
14753     }
14754 }
14755
14756 void
14757 TrainingEvent ()
14758 {
14759     if (gameMode == Training) {
14760       SetTrainingModeOff();
14761       gameMode = PlayFromGameFile;
14762       DisplayMessage("", _("Training mode off"));
14763     } else {
14764       gameMode = Training;
14765       animateTraining = appData.animate;
14766
14767       /* make sure we are not already at the end of the game */
14768       if (currentMove < forwardMostMove) {
14769         SetTrainingModeOn();
14770         DisplayMessage("", _("Training mode on"));
14771       } else {
14772         gameMode = PlayFromGameFile;
14773         DisplayError(_("Already at end of game"), 0);
14774       }
14775     }
14776     ModeHighlight();
14777 }
14778
14779 void
14780 IcsClientEvent ()
14781 {
14782     if (!appData.icsActive) return;
14783     switch (gameMode) {
14784       case IcsPlayingWhite:
14785       case IcsPlayingBlack:
14786       case IcsObserving:
14787       case IcsIdle:
14788       case BeginningOfGame:
14789       case IcsExamining:
14790         return;
14791
14792       case EditGame:
14793         break;
14794
14795       case EditPosition:
14796         EditPositionDone(TRUE);
14797         break;
14798
14799       case AnalyzeMode:
14800       case AnalyzeFile:
14801         ExitAnalyzeMode();
14802         break;
14803
14804       default:
14805         EditGameEvent();
14806         break;
14807     }
14808
14809     gameMode = IcsIdle;
14810     ModeHighlight();
14811     return;
14812 }
14813
14814 void
14815 EditGameEvent ()
14816 {
14817     int i;
14818
14819     switch (gameMode) {
14820       case Training:
14821         SetTrainingModeOff();
14822         break;
14823       case MachinePlaysWhite:
14824       case MachinePlaysBlack:
14825       case BeginningOfGame:
14826         SendToProgram("force\n", &first);
14827         SetUserThinkingEnables();
14828         break;
14829       case PlayFromGameFile:
14830         (void) StopLoadGameTimer();
14831         if (gameFileFP != NULL) {
14832             gameFileFP = NULL;
14833         }
14834         break;
14835       case EditPosition:
14836         EditPositionDone(TRUE);
14837         break;
14838       case AnalyzeMode:
14839       case AnalyzeFile:
14840         ExitAnalyzeMode();
14841         SendToProgram("force\n", &first);
14842         break;
14843       case TwoMachinesPlay:
14844         GameEnds(EndOfFile, NULL, GE_PLAYER);
14845         ResurrectChessProgram();
14846         SetUserThinkingEnables();
14847         break;
14848       case EndOfGame:
14849         ResurrectChessProgram();
14850         break;
14851       case IcsPlayingBlack:
14852       case IcsPlayingWhite:
14853         DisplayError(_("Warning: You are still playing a game"), 0);
14854         break;
14855       case IcsObserving:
14856         DisplayError(_("Warning: You are still observing a game"), 0);
14857         break;
14858       case IcsExamining:
14859         DisplayError(_("Warning: You are still examining a game"), 0);
14860         break;
14861       case IcsIdle:
14862         break;
14863       case EditGame:
14864       default:
14865         return;
14866     }
14867
14868     pausing = FALSE;
14869     StopClocks();
14870     first.offeredDraw = second.offeredDraw = 0;
14871
14872     if (gameMode == PlayFromGameFile) {
14873         whiteTimeRemaining = timeRemaining[0][currentMove];
14874         blackTimeRemaining = timeRemaining[1][currentMove];
14875         DisplayTitle("");
14876     }
14877
14878     if (gameMode == MachinePlaysWhite ||
14879         gameMode == MachinePlaysBlack ||
14880         gameMode == TwoMachinesPlay ||
14881         gameMode == EndOfGame) {
14882         i = forwardMostMove;
14883         while (i > currentMove) {
14884             SendToProgram("undo\n", &first);
14885             i--;
14886         }
14887         if(!adjustedClock) {
14888         whiteTimeRemaining = timeRemaining[0][currentMove];
14889         blackTimeRemaining = timeRemaining[1][currentMove];
14890         DisplayBothClocks();
14891         }
14892         if (whiteFlag || blackFlag) {
14893             whiteFlag = blackFlag = 0;
14894         }
14895         DisplayTitle("");
14896     }
14897
14898     gameMode = EditGame;
14899     ModeHighlight();
14900     SetGameInfo();
14901 }
14902
14903
14904 void
14905 EditPositionEvent ()
14906 {
14907     if (gameMode == EditPosition) {
14908         EditGameEvent();
14909         return;
14910     }
14911
14912     EditGameEvent();
14913     if (gameMode != EditGame) return;
14914
14915     gameMode = EditPosition;
14916     ModeHighlight();
14917     SetGameInfo();
14918     if (currentMove > 0)
14919       CopyBoard(boards[0], boards[currentMove]);
14920
14921     blackPlaysFirst = !WhiteOnMove(currentMove);
14922     ResetClocks();
14923     currentMove = forwardMostMove = backwardMostMove = 0;
14924     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14925     DisplayMove(-1);
14926     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14927 }
14928
14929 void
14930 ExitAnalyzeMode ()
14931 {
14932     /* [DM] icsEngineAnalyze - possible call from other functions */
14933     if (appData.icsEngineAnalyze) {
14934         appData.icsEngineAnalyze = FALSE;
14935
14936         DisplayMessage("",_("Close ICS engine analyze..."));
14937     }
14938     if (first.analysisSupport && first.analyzing) {
14939       SendToBoth("exit\n");
14940       first.analyzing = second.analyzing = FALSE;
14941     }
14942     thinkOutput[0] = NULLCHAR;
14943 }
14944
14945 void
14946 EditPositionDone (Boolean fakeRights)
14947 {
14948     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14949
14950     startedFromSetupPosition = TRUE;
14951     InitChessProgram(&first, FALSE);
14952     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14953       boards[0][EP_STATUS] = EP_NONE;
14954       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14955       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14956         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14957         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14958       } else boards[0][CASTLING][2] = NoRights;
14959       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14960         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14961         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14962       } else boards[0][CASTLING][5] = NoRights;
14963       if(gameInfo.variant == VariantSChess) {
14964         int i;
14965         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14966           boards[0][VIRGIN][i] = 0;
14967           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14968           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14969         }
14970       }
14971     }
14972     SendToProgram("force\n", &first);
14973     if (blackPlaysFirst) {
14974         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14975         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14976         currentMove = forwardMostMove = backwardMostMove = 1;
14977         CopyBoard(boards[1], boards[0]);
14978     } else {
14979         currentMove = forwardMostMove = backwardMostMove = 0;
14980     }
14981     SendBoard(&first, forwardMostMove);
14982     if (appData.debugMode) {
14983         fprintf(debugFP, "EditPosDone\n");
14984     }
14985     DisplayTitle("");
14986     DisplayMessage("", "");
14987     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14988     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14989     gameMode = EditGame;
14990     ModeHighlight();
14991     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14992     ClearHighlights(); /* [AS] */
14993 }
14994
14995 /* Pause for `ms' milliseconds */
14996 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14997 void
14998 TimeDelay (long ms)
14999 {
15000     TimeMark m1, m2;
15001
15002     GetTimeMark(&m1);
15003     do {
15004         GetTimeMark(&m2);
15005     } while (SubtractTimeMarks(&m2, &m1) < ms);
15006 }
15007
15008 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15009 void
15010 SendMultiLineToICS (char *buf)
15011 {
15012     char temp[MSG_SIZ+1], *p;
15013     int len;
15014
15015     len = strlen(buf);
15016     if (len > MSG_SIZ)
15017       len = MSG_SIZ;
15018
15019     strncpy(temp, buf, len);
15020     temp[len] = 0;
15021
15022     p = temp;
15023     while (*p) {
15024         if (*p == '\n' || *p == '\r')
15025           *p = ' ';
15026         ++p;
15027     }
15028
15029     strcat(temp, "\n");
15030     SendToICS(temp);
15031     SendToPlayer(temp, strlen(temp));
15032 }
15033
15034 void
15035 SetWhiteToPlayEvent ()
15036 {
15037     if (gameMode == EditPosition) {
15038         blackPlaysFirst = FALSE;
15039         DisplayBothClocks();    /* works because currentMove is 0 */
15040     } else if (gameMode == IcsExamining) {
15041         SendToICS(ics_prefix);
15042         SendToICS("tomove white\n");
15043     }
15044 }
15045
15046 void
15047 SetBlackToPlayEvent ()
15048 {
15049     if (gameMode == EditPosition) {
15050         blackPlaysFirst = TRUE;
15051         currentMove = 1;        /* kludge */
15052         DisplayBothClocks();
15053         currentMove = 0;
15054     } else if (gameMode == IcsExamining) {
15055         SendToICS(ics_prefix);
15056         SendToICS("tomove black\n");
15057     }
15058 }
15059
15060 void
15061 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15062 {
15063     char buf[MSG_SIZ];
15064     ChessSquare piece = boards[0][y][x];
15065     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15066     static int lastVariant;
15067
15068     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15069
15070     switch (selection) {
15071       case ClearBoard:
15072         CopyBoard(currentBoard, boards[0]);
15073         CopyBoard(menuBoard, initialPosition);
15074         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15075             SendToICS(ics_prefix);
15076             SendToICS("bsetup clear\n");
15077         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15078             SendToICS(ics_prefix);
15079             SendToICS("clearboard\n");
15080         } else {
15081             int nonEmpty = 0;
15082             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15083                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15084                 for (y = 0; y < BOARD_HEIGHT; y++) {
15085                     if (gameMode == IcsExamining) {
15086                         if (boards[currentMove][y][x] != EmptySquare) {
15087                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15088                                     AAA + x, ONE + y);
15089                             SendToICS(buf);
15090                         }
15091                     } else {
15092                         if(boards[0][y][x] != p) nonEmpty++;
15093                         boards[0][y][x] = p;
15094                     }
15095                 }
15096             }
15097             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15098                 int r;
15099                 for(r = 0; r < BOARD_HEIGHT; r++) {
15100                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15101                     ChessSquare p = menuBoard[r][x];
15102                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15103                   }
15104                 }
15105                 DisplayMessage("Clicking clock again restores position", "");
15106                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15107                 if(!nonEmpty) { // asked to clear an empty board
15108                     CopyBoard(boards[0], menuBoard);
15109                 } else
15110                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15111                     CopyBoard(boards[0], initialPosition);
15112                 } else
15113                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15114                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15115                     CopyBoard(boards[0], erasedBoard);
15116                 } else
15117                     CopyBoard(erasedBoard, currentBoard);
15118
15119             }
15120         }
15121         if (gameMode == EditPosition) {
15122             DrawPosition(FALSE, boards[0]);
15123         }
15124         break;
15125
15126       case WhitePlay:
15127         SetWhiteToPlayEvent();
15128         break;
15129
15130       case BlackPlay:
15131         SetBlackToPlayEvent();
15132         break;
15133
15134       case EmptySquare:
15135         if (gameMode == IcsExamining) {
15136             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15137             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15138             SendToICS(buf);
15139         } else {
15140             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15141                 if(x == BOARD_LEFT-2) {
15142                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15143                     boards[0][y][1] = 0;
15144                 } else
15145                 if(x == BOARD_RGHT+1) {
15146                     if(y >= gameInfo.holdingsSize) break;
15147                     boards[0][y][BOARD_WIDTH-2] = 0;
15148                 } else break;
15149             }
15150             boards[0][y][x] = EmptySquare;
15151             DrawPosition(FALSE, boards[0]);
15152         }
15153         break;
15154
15155       case PromotePiece:
15156         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15157            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15158             selection = (ChessSquare) (PROMOTED piece);
15159         } else if(piece == EmptySquare) selection = WhiteSilver;
15160         else selection = (ChessSquare)((int)piece - 1);
15161         goto defaultlabel;
15162
15163       case DemotePiece:
15164         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15165            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15166             selection = (ChessSquare) (DEMOTED piece);
15167         } else if(piece == EmptySquare) selection = BlackSilver;
15168         else selection = (ChessSquare)((int)piece + 1);
15169         goto defaultlabel;
15170
15171       case WhiteQueen:
15172       case BlackQueen:
15173         if(gameInfo.variant == VariantShatranj ||
15174            gameInfo.variant == VariantXiangqi  ||
15175            gameInfo.variant == VariantCourier  ||
15176            gameInfo.variant == VariantASEAN    ||
15177            gameInfo.variant == VariantMakruk     )
15178             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15179         goto defaultlabel;
15180
15181       case WhiteKing:
15182       case BlackKing:
15183         if(gameInfo.variant == VariantXiangqi)
15184             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15185         if(gameInfo.variant == VariantKnightmate)
15186             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15187       default:
15188         defaultlabel:
15189         if (gameMode == IcsExamining) {
15190             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15191             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15192                      PieceToChar(selection), AAA + x, ONE + y);
15193             SendToICS(buf);
15194         } else {
15195             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15196                 int n;
15197                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15198                     n = PieceToNumber(selection - BlackPawn);
15199                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15200                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15201                     boards[0][BOARD_HEIGHT-1-n][1]++;
15202                 } else
15203                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15204                     n = PieceToNumber(selection);
15205                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15206                     boards[0][n][BOARD_WIDTH-1] = selection;
15207                     boards[0][n][BOARD_WIDTH-2]++;
15208                 }
15209             } else
15210             boards[0][y][x] = selection;
15211             DrawPosition(TRUE, boards[0]);
15212             ClearHighlights();
15213             fromX = fromY = -1;
15214         }
15215         break;
15216     }
15217 }
15218
15219
15220 void
15221 DropMenuEvent (ChessSquare selection, int x, int y)
15222 {
15223     ChessMove moveType;
15224
15225     switch (gameMode) {
15226       case IcsPlayingWhite:
15227       case MachinePlaysBlack:
15228         if (!WhiteOnMove(currentMove)) {
15229             DisplayMoveError(_("It is Black's turn"));
15230             return;
15231         }
15232         moveType = WhiteDrop;
15233         break;
15234       case IcsPlayingBlack:
15235       case MachinePlaysWhite:
15236         if (WhiteOnMove(currentMove)) {
15237             DisplayMoveError(_("It is White's turn"));
15238             return;
15239         }
15240         moveType = BlackDrop;
15241         break;
15242       case EditGame:
15243         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15244         break;
15245       default:
15246         return;
15247     }
15248
15249     if (moveType == BlackDrop && selection < BlackPawn) {
15250       selection = (ChessSquare) ((int) selection
15251                                  + (int) BlackPawn - (int) WhitePawn);
15252     }
15253     if (boards[currentMove][y][x] != EmptySquare) {
15254         DisplayMoveError(_("That square is occupied"));
15255         return;
15256     }
15257
15258     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15259 }
15260
15261 void
15262 AcceptEvent ()
15263 {
15264     /* Accept a pending offer of any kind from opponent */
15265
15266     if (appData.icsActive) {
15267         SendToICS(ics_prefix);
15268         SendToICS("accept\n");
15269     } else if (cmailMsgLoaded) {
15270         if (currentMove == cmailOldMove &&
15271             commentList[cmailOldMove] != NULL &&
15272             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15273                    "Black offers a draw" : "White offers a draw")) {
15274             TruncateGame();
15275             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15276             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15277         } else {
15278             DisplayError(_("There is no pending offer on this move"), 0);
15279             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15280         }
15281     } else {
15282         /* Not used for offers from chess program */
15283     }
15284 }
15285
15286 void
15287 DeclineEvent ()
15288 {
15289     /* Decline a pending offer of any kind from opponent */
15290
15291     if (appData.icsActive) {
15292         SendToICS(ics_prefix);
15293         SendToICS("decline\n");
15294     } else if (cmailMsgLoaded) {
15295         if (currentMove == cmailOldMove &&
15296             commentList[cmailOldMove] != NULL &&
15297             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15298                    "Black offers a draw" : "White offers a draw")) {
15299 #ifdef NOTDEF
15300             AppendComment(cmailOldMove, "Draw declined", TRUE);
15301             DisplayComment(cmailOldMove - 1, "Draw declined");
15302 #endif /*NOTDEF*/
15303         } else {
15304             DisplayError(_("There is no pending offer on this move"), 0);
15305         }
15306     } else {
15307         /* Not used for offers from chess program */
15308     }
15309 }
15310
15311 void
15312 RematchEvent ()
15313 {
15314     /* Issue ICS rematch command */
15315     if (appData.icsActive) {
15316         SendToICS(ics_prefix);
15317         SendToICS("rematch\n");
15318     }
15319 }
15320
15321 void
15322 CallFlagEvent ()
15323 {
15324     /* Call your opponent's flag (claim a win on time) */
15325     if (appData.icsActive) {
15326         SendToICS(ics_prefix);
15327         SendToICS("flag\n");
15328     } else {
15329         switch (gameMode) {
15330           default:
15331             return;
15332           case MachinePlaysWhite:
15333             if (whiteFlag) {
15334                 if (blackFlag)
15335                   GameEnds(GameIsDrawn, "Both players ran out of time",
15336                            GE_PLAYER);
15337                 else
15338                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15339             } else {
15340                 DisplayError(_("Your opponent is not out of time"), 0);
15341             }
15342             break;
15343           case MachinePlaysBlack:
15344             if (blackFlag) {
15345                 if (whiteFlag)
15346                   GameEnds(GameIsDrawn, "Both players ran out of time",
15347                            GE_PLAYER);
15348                 else
15349                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15350             } else {
15351                 DisplayError(_("Your opponent is not out of time"), 0);
15352             }
15353             break;
15354         }
15355     }
15356 }
15357
15358 void
15359 ClockClick (int which)
15360 {       // [HGM] code moved to back-end from winboard.c
15361         if(which) { // black clock
15362           if (gameMode == EditPosition || gameMode == IcsExamining) {
15363             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15364             SetBlackToPlayEvent();
15365           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15366                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15367           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15368           } else if (shiftKey) {
15369             AdjustClock(which, -1);
15370           } else if (gameMode == IcsPlayingWhite ||
15371                      gameMode == MachinePlaysBlack) {
15372             CallFlagEvent();
15373           }
15374         } else { // white clock
15375           if (gameMode == EditPosition || gameMode == IcsExamining) {
15376             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15377             SetWhiteToPlayEvent();
15378           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15379                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15380           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15381           } else if (shiftKey) {
15382             AdjustClock(which, -1);
15383           } else if (gameMode == IcsPlayingBlack ||
15384                    gameMode == MachinePlaysWhite) {
15385             CallFlagEvent();
15386           }
15387         }
15388 }
15389
15390 void
15391 DrawEvent ()
15392 {
15393     /* Offer draw or accept pending draw offer from opponent */
15394
15395     if (appData.icsActive) {
15396         /* Note: tournament rules require draw offers to be
15397            made after you make your move but before you punch
15398            your clock.  Currently ICS doesn't let you do that;
15399            instead, you immediately punch your clock after making
15400            a move, but you can offer a draw at any time. */
15401
15402         SendToICS(ics_prefix);
15403         SendToICS("draw\n");
15404         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15405     } else if (cmailMsgLoaded) {
15406         if (currentMove == cmailOldMove &&
15407             commentList[cmailOldMove] != NULL &&
15408             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15409                    "Black offers a draw" : "White offers a draw")) {
15410             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15411             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15412         } else if (currentMove == cmailOldMove + 1) {
15413             char *offer = WhiteOnMove(cmailOldMove) ?
15414               "White offers a draw" : "Black offers a draw";
15415             AppendComment(currentMove, offer, TRUE);
15416             DisplayComment(currentMove - 1, offer);
15417             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15418         } else {
15419             DisplayError(_("You must make your move before offering a draw"), 0);
15420             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15421         }
15422     } else if (first.offeredDraw) {
15423         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15424     } else {
15425         if (first.sendDrawOffers) {
15426             SendToProgram("draw\n", &first);
15427             userOfferedDraw = TRUE;
15428         }
15429     }
15430 }
15431
15432 void
15433 AdjournEvent ()
15434 {
15435     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15436
15437     if (appData.icsActive) {
15438         SendToICS(ics_prefix);
15439         SendToICS("adjourn\n");
15440     } else {
15441         /* Currently GNU Chess doesn't offer or accept Adjourns */
15442     }
15443 }
15444
15445
15446 void
15447 AbortEvent ()
15448 {
15449     /* Offer Abort or accept pending Abort offer from opponent */
15450
15451     if (appData.icsActive) {
15452         SendToICS(ics_prefix);
15453         SendToICS("abort\n");
15454     } else {
15455         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15456     }
15457 }
15458
15459 void
15460 ResignEvent ()
15461 {
15462     /* Resign.  You can do this even if it's not your turn. */
15463
15464     if (appData.icsActive) {
15465         SendToICS(ics_prefix);
15466         SendToICS("resign\n");
15467     } else {
15468         switch (gameMode) {
15469           case MachinePlaysWhite:
15470             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15471             break;
15472           case MachinePlaysBlack:
15473             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15474             break;
15475           case EditGame:
15476             if (cmailMsgLoaded) {
15477                 TruncateGame();
15478                 if (WhiteOnMove(cmailOldMove)) {
15479                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15480                 } else {
15481                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15482                 }
15483                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15484             }
15485             break;
15486           default:
15487             break;
15488         }
15489     }
15490 }
15491
15492
15493 void
15494 StopObservingEvent ()
15495 {
15496     /* Stop observing current games */
15497     SendToICS(ics_prefix);
15498     SendToICS("unobserve\n");
15499 }
15500
15501 void
15502 StopExaminingEvent ()
15503 {
15504     /* Stop observing current game */
15505     SendToICS(ics_prefix);
15506     SendToICS("unexamine\n");
15507 }
15508
15509 void
15510 ForwardInner (int target)
15511 {
15512     int limit; int oldSeekGraphUp = seekGraphUp;
15513
15514     if (appData.debugMode)
15515         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15516                 target, currentMove, forwardMostMove);
15517
15518     if (gameMode == EditPosition)
15519       return;
15520
15521     seekGraphUp = FALSE;
15522     MarkTargetSquares(1);
15523
15524     if (gameMode == PlayFromGameFile && !pausing)
15525       PauseEvent();
15526
15527     if (gameMode == IcsExamining && pausing)
15528       limit = pauseExamForwardMostMove;
15529     else
15530       limit = forwardMostMove;
15531
15532     if (target > limit) target = limit;
15533
15534     if (target > 0 && moveList[target - 1][0]) {
15535         int fromX, fromY, toX, toY;
15536         toX = moveList[target - 1][2] - AAA;
15537         toY = moveList[target - 1][3] - ONE;
15538         if (moveList[target - 1][1] == '@') {
15539             if (appData.highlightLastMove) {
15540                 SetHighlights(-1, -1, toX, toY);
15541             }
15542         } else {
15543             int viaX = moveList[target - 1][5] - AAA;
15544             int viaY = moveList[target - 1][6] - ONE;
15545             fromX = moveList[target - 1][0] - AAA;
15546             fromY = moveList[target - 1][1] - ONE;
15547             if (target == currentMove + 1) {
15548                 if(moveList[target - 1][4] == ';') { // multi-leg
15549                     ChessSquare piece = boards[currentMove][viaY][viaX];
15550                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15551                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15552                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15553                     boards[currentMove][viaY][viaX] = piece;
15554                 } else
15555                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15556             }
15557             if (appData.highlightLastMove) {
15558                 SetHighlights(fromX, fromY, toX, toY);
15559             }
15560         }
15561     }
15562     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15563         gameMode == Training || gameMode == PlayFromGameFile ||
15564         gameMode == AnalyzeFile) {
15565         while (currentMove < target) {
15566             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15567             SendMoveToProgram(currentMove++, &first);
15568         }
15569     } else {
15570         currentMove = target;
15571     }
15572
15573     if (gameMode == EditGame || gameMode == EndOfGame) {
15574         whiteTimeRemaining = timeRemaining[0][currentMove];
15575         blackTimeRemaining = timeRemaining[1][currentMove];
15576     }
15577     DisplayBothClocks();
15578     DisplayMove(currentMove - 1);
15579     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15580     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15581     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15582         DisplayComment(currentMove - 1, commentList[currentMove]);
15583     }
15584     ClearMap(); // [HGM] exclude: invalidate map
15585 }
15586
15587
15588 void
15589 ForwardEvent ()
15590 {
15591     if (gameMode == IcsExamining && !pausing) {
15592         SendToICS(ics_prefix);
15593         SendToICS("forward\n");
15594     } else {
15595         ForwardInner(currentMove + 1);
15596     }
15597 }
15598
15599 void
15600 ToEndEvent ()
15601 {
15602     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15603         /* to optimze, we temporarily turn off analysis mode while we feed
15604          * the remaining moves to the engine. Otherwise we get analysis output
15605          * after each move.
15606          */
15607         if (first.analysisSupport) {
15608           SendToProgram("exit\nforce\n", &first);
15609           first.analyzing = FALSE;
15610         }
15611     }
15612
15613     if (gameMode == IcsExamining && !pausing) {
15614         SendToICS(ics_prefix);
15615         SendToICS("forward 999999\n");
15616     } else {
15617         ForwardInner(forwardMostMove);
15618     }
15619
15620     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15621         /* we have fed all the moves, so reactivate analysis mode */
15622         SendToProgram("analyze\n", &first);
15623         first.analyzing = TRUE;
15624         /*first.maybeThinking = TRUE;*/
15625         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15626     }
15627 }
15628
15629 void
15630 BackwardInner (int target)
15631 {
15632     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15633
15634     if (appData.debugMode)
15635         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15636                 target, currentMove, forwardMostMove);
15637
15638     if (gameMode == EditPosition) return;
15639     seekGraphUp = FALSE;
15640     MarkTargetSquares(1);
15641     if (currentMove <= backwardMostMove) {
15642         ClearHighlights();
15643         DrawPosition(full_redraw, boards[currentMove]);
15644         return;
15645     }
15646     if (gameMode == PlayFromGameFile && !pausing)
15647       PauseEvent();
15648
15649     if (moveList[target][0]) {
15650         int fromX, fromY, toX, toY;
15651         toX = moveList[target][2] - AAA;
15652         toY = moveList[target][3] - ONE;
15653         if (moveList[target][1] == '@') {
15654             if (appData.highlightLastMove) {
15655                 SetHighlights(-1, -1, toX, toY);
15656             }
15657         } else {
15658             fromX = moveList[target][0] - AAA;
15659             fromY = moveList[target][1] - ONE;
15660             if (target == currentMove - 1) {
15661                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15662             }
15663             if (appData.highlightLastMove) {
15664                 SetHighlights(fromX, fromY, toX, toY);
15665             }
15666         }
15667     }
15668     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15669         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15670         while (currentMove > target) {
15671             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15672                 // null move cannot be undone. Reload program with move history before it.
15673                 int i;
15674                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15675                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15676                 }
15677                 SendBoard(&first, i);
15678               if(second.analyzing) SendBoard(&second, i);
15679                 for(currentMove=i; currentMove<target; currentMove++) {
15680                     SendMoveToProgram(currentMove, &first);
15681                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15682                 }
15683                 break;
15684             }
15685             SendToBoth("undo\n");
15686             currentMove--;
15687         }
15688     } else {
15689         currentMove = target;
15690     }
15691
15692     if (gameMode == EditGame || gameMode == EndOfGame) {
15693         whiteTimeRemaining = timeRemaining[0][currentMove];
15694         blackTimeRemaining = timeRemaining[1][currentMove];
15695     }
15696     DisplayBothClocks();
15697     DisplayMove(currentMove - 1);
15698     DrawPosition(full_redraw, boards[currentMove]);
15699     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15700     // [HGM] PV info: routine tests if comment empty
15701     DisplayComment(currentMove - 1, commentList[currentMove]);
15702     ClearMap(); // [HGM] exclude: invalidate map
15703 }
15704
15705 void
15706 BackwardEvent ()
15707 {
15708     if (gameMode == IcsExamining && !pausing) {
15709         SendToICS(ics_prefix);
15710         SendToICS("backward\n");
15711     } else {
15712         BackwardInner(currentMove - 1);
15713     }
15714 }
15715
15716 void
15717 ToStartEvent ()
15718 {
15719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15720         /* to optimize, we temporarily turn off analysis mode while we undo
15721          * all the moves. Otherwise we get analysis output after each undo.
15722          */
15723         if (first.analysisSupport) {
15724           SendToProgram("exit\nforce\n", &first);
15725           first.analyzing = FALSE;
15726         }
15727     }
15728
15729     if (gameMode == IcsExamining && !pausing) {
15730         SendToICS(ics_prefix);
15731         SendToICS("backward 999999\n");
15732     } else {
15733         BackwardInner(backwardMostMove);
15734     }
15735
15736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15737         /* we have fed all the moves, so reactivate analysis mode */
15738         SendToProgram("analyze\n", &first);
15739         first.analyzing = TRUE;
15740         /*first.maybeThinking = TRUE;*/
15741         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15742     }
15743 }
15744
15745 void
15746 ToNrEvent (int to)
15747 {
15748   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15749   if (to >= forwardMostMove) to = forwardMostMove;
15750   if (to <= backwardMostMove) to = backwardMostMove;
15751   if (to < currentMove) {
15752     BackwardInner(to);
15753   } else {
15754     ForwardInner(to);
15755   }
15756 }
15757
15758 void
15759 RevertEvent (Boolean annotate)
15760 {
15761     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15762         return;
15763     }
15764     if (gameMode != IcsExamining) {
15765         DisplayError(_("You are not examining a game"), 0);
15766         return;
15767     }
15768     if (pausing) {
15769         DisplayError(_("You can't revert while pausing"), 0);
15770         return;
15771     }
15772     SendToICS(ics_prefix);
15773     SendToICS("revert\n");
15774 }
15775
15776 void
15777 RetractMoveEvent ()
15778 {
15779     switch (gameMode) {
15780       case MachinePlaysWhite:
15781       case MachinePlaysBlack:
15782         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15783             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15784             return;
15785         }
15786         if (forwardMostMove < 2) return;
15787         currentMove = forwardMostMove = forwardMostMove - 2;
15788         whiteTimeRemaining = timeRemaining[0][currentMove];
15789         blackTimeRemaining = timeRemaining[1][currentMove];
15790         DisplayBothClocks();
15791         DisplayMove(currentMove - 1);
15792         ClearHighlights();/*!! could figure this out*/
15793         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15794         SendToProgram("remove\n", &first);
15795         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15796         break;
15797
15798       case BeginningOfGame:
15799       default:
15800         break;
15801
15802       case IcsPlayingWhite:
15803       case IcsPlayingBlack:
15804         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15805             SendToICS(ics_prefix);
15806             SendToICS("takeback 2\n");
15807         } else {
15808             SendToICS(ics_prefix);
15809             SendToICS("takeback 1\n");
15810         }
15811         break;
15812     }
15813 }
15814
15815 void
15816 MoveNowEvent ()
15817 {
15818     ChessProgramState *cps;
15819
15820     switch (gameMode) {
15821       case MachinePlaysWhite:
15822         if (!WhiteOnMove(forwardMostMove)) {
15823             DisplayError(_("It is your turn"), 0);
15824             return;
15825         }
15826         cps = &first;
15827         break;
15828       case MachinePlaysBlack:
15829         if (WhiteOnMove(forwardMostMove)) {
15830             DisplayError(_("It is your turn"), 0);
15831             return;
15832         }
15833         cps = &first;
15834         break;
15835       case TwoMachinesPlay:
15836         if (WhiteOnMove(forwardMostMove) ==
15837             (first.twoMachinesColor[0] == 'w')) {
15838             cps = &first;
15839         } else {
15840             cps = &second;
15841         }
15842         break;
15843       case BeginningOfGame:
15844       default:
15845         return;
15846     }
15847     SendToProgram("?\n", cps);
15848 }
15849
15850 void
15851 TruncateGameEvent ()
15852 {
15853     EditGameEvent();
15854     if (gameMode != EditGame) return;
15855     TruncateGame();
15856 }
15857
15858 void
15859 TruncateGame ()
15860 {
15861     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15862     if (forwardMostMove > currentMove) {
15863         if (gameInfo.resultDetails != NULL) {
15864             free(gameInfo.resultDetails);
15865             gameInfo.resultDetails = NULL;
15866             gameInfo.result = GameUnfinished;
15867         }
15868         forwardMostMove = currentMove;
15869         HistorySet(parseList, backwardMostMove, forwardMostMove,
15870                    currentMove-1);
15871     }
15872 }
15873
15874 void
15875 HintEvent ()
15876 {
15877     if (appData.noChessProgram) return;
15878     switch (gameMode) {
15879       case MachinePlaysWhite:
15880         if (WhiteOnMove(forwardMostMove)) {
15881             DisplayError(_("Wait until your turn."), 0);
15882             return;
15883         }
15884         break;
15885       case BeginningOfGame:
15886       case MachinePlaysBlack:
15887         if (!WhiteOnMove(forwardMostMove)) {
15888             DisplayError(_("Wait until your turn."), 0);
15889             return;
15890         }
15891         break;
15892       default:
15893         DisplayError(_("No hint available"), 0);
15894         return;
15895     }
15896     SendToProgram("hint\n", &first);
15897     hintRequested = TRUE;
15898 }
15899
15900 int
15901 SaveSelected (FILE *g, int dummy, char *dummy2)
15902 {
15903     ListGame * lg = (ListGame *) gameList.head;
15904     int nItem, cnt=0;
15905     FILE *f;
15906
15907     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15908         DisplayError(_("Game list not loaded or empty"), 0);
15909         return 0;
15910     }
15911
15912     creatingBook = TRUE; // suppresses stuff during load game
15913
15914     /* Get list size */
15915     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15916         if(lg->position >= 0) { // selected?
15917             LoadGame(f, nItem, "", TRUE);
15918             SaveGamePGN2(g); // leaves g open
15919             cnt++; DoEvents();
15920         }
15921         lg = (ListGame *) lg->node.succ;
15922     }
15923
15924     fclose(g);
15925     creatingBook = FALSE;
15926
15927     return cnt;
15928 }
15929
15930 void
15931 CreateBookEvent ()
15932 {
15933     ListGame * lg = (ListGame *) gameList.head;
15934     FILE *f, *g;
15935     int nItem;
15936     static int secondTime = FALSE;
15937
15938     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15939         DisplayError(_("Game list not loaded or empty"), 0);
15940         return;
15941     }
15942
15943     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15944         fclose(g);
15945         secondTime++;
15946         DisplayNote(_("Book file exists! Try again for overwrite."));
15947         return;
15948     }
15949
15950     creatingBook = TRUE;
15951     secondTime = FALSE;
15952
15953     /* Get list size */
15954     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15955         if(lg->position >= 0) {
15956             LoadGame(f, nItem, "", TRUE);
15957             AddGameToBook(TRUE);
15958             DoEvents();
15959         }
15960         lg = (ListGame *) lg->node.succ;
15961     }
15962
15963     creatingBook = FALSE;
15964     FlushBook();
15965 }
15966
15967 void
15968 BookEvent ()
15969 {
15970     if (appData.noChessProgram) return;
15971     switch (gameMode) {
15972       case MachinePlaysWhite:
15973         if (WhiteOnMove(forwardMostMove)) {
15974             DisplayError(_("Wait until your turn."), 0);
15975             return;
15976         }
15977         break;
15978       case BeginningOfGame:
15979       case MachinePlaysBlack:
15980         if (!WhiteOnMove(forwardMostMove)) {
15981             DisplayError(_("Wait until your turn."), 0);
15982             return;
15983         }
15984         break;
15985       case EditPosition:
15986         EditPositionDone(TRUE);
15987         break;
15988       case TwoMachinesPlay:
15989         return;
15990       default:
15991         break;
15992     }
15993     SendToProgram("bk\n", &first);
15994     bookOutput[0] = NULLCHAR;
15995     bookRequested = TRUE;
15996 }
15997
15998 void
15999 AboutGameEvent ()
16000 {
16001     char *tags = PGNTags(&gameInfo);
16002     TagsPopUp(tags, CmailMsg());
16003     free(tags);
16004 }
16005
16006 /* end button procedures */
16007
16008 void
16009 PrintPosition (FILE *fp, int move)
16010 {
16011     int i, j;
16012
16013     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16014         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16015             char c = PieceToChar(boards[move][i][j]);
16016             fputc(c == 'x' ? '.' : c, fp);
16017             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16018         }
16019     }
16020     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16021       fprintf(fp, "white to play\n");
16022     else
16023       fprintf(fp, "black to play\n");
16024 }
16025
16026 void
16027 PrintOpponents (FILE *fp)
16028 {
16029     if (gameInfo.white != NULL) {
16030         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16031     } else {
16032         fprintf(fp, "\n");
16033     }
16034 }
16035
16036 /* Find last component of program's own name, using some heuristics */
16037 void
16038 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16039 {
16040     char *p, *q, c;
16041     int local = (strcmp(host, "localhost") == 0);
16042     while (!local && (p = strchr(prog, ';')) != NULL) {
16043         p++;
16044         while (*p == ' ') p++;
16045         prog = p;
16046     }
16047     if (*prog == '"' || *prog == '\'') {
16048         q = strchr(prog + 1, *prog);
16049     } else {
16050         q = strchr(prog, ' ');
16051     }
16052     if (q == NULL) q = prog + strlen(prog);
16053     p = q;
16054     while (p >= prog && *p != '/' && *p != '\\') p--;
16055     p++;
16056     if(p == prog && *p == '"') p++;
16057     c = *q; *q = 0;
16058     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16059     memcpy(buf, p, q - p);
16060     buf[q - p] = NULLCHAR;
16061     if (!local) {
16062         strcat(buf, "@");
16063         strcat(buf, host);
16064     }
16065 }
16066
16067 char *
16068 TimeControlTagValue ()
16069 {
16070     char buf[MSG_SIZ];
16071     if (!appData.clockMode) {
16072       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16073     } else if (movesPerSession > 0) {
16074       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16075     } else if (timeIncrement == 0) {
16076       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16077     } else {
16078       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16079     }
16080     return StrSave(buf);
16081 }
16082
16083 void
16084 SetGameInfo ()
16085 {
16086     /* This routine is used only for certain modes */
16087     VariantClass v = gameInfo.variant;
16088     ChessMove r = GameUnfinished;
16089     char *p = NULL;
16090
16091     if(keepInfo) return;
16092
16093     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16094         r = gameInfo.result;
16095         p = gameInfo.resultDetails;
16096         gameInfo.resultDetails = NULL;
16097     }
16098     ClearGameInfo(&gameInfo);
16099     gameInfo.variant = v;
16100
16101     switch (gameMode) {
16102       case MachinePlaysWhite:
16103         gameInfo.event = StrSave( appData.pgnEventHeader );
16104         gameInfo.site = StrSave(HostName());
16105         gameInfo.date = PGNDate();
16106         gameInfo.round = StrSave("-");
16107         gameInfo.white = StrSave(first.tidy);
16108         gameInfo.black = StrSave(UserName());
16109         gameInfo.timeControl = TimeControlTagValue();
16110         break;
16111
16112       case MachinePlaysBlack:
16113         gameInfo.event = StrSave( appData.pgnEventHeader );
16114         gameInfo.site = StrSave(HostName());
16115         gameInfo.date = PGNDate();
16116         gameInfo.round = StrSave("-");
16117         gameInfo.white = StrSave(UserName());
16118         gameInfo.black = StrSave(first.tidy);
16119         gameInfo.timeControl = TimeControlTagValue();
16120         break;
16121
16122       case TwoMachinesPlay:
16123         gameInfo.event = StrSave( appData.pgnEventHeader );
16124         gameInfo.site = StrSave(HostName());
16125         gameInfo.date = PGNDate();
16126         if (roundNr > 0) {
16127             char buf[MSG_SIZ];
16128             snprintf(buf, MSG_SIZ, "%d", roundNr);
16129             gameInfo.round = StrSave(buf);
16130         } else {
16131             gameInfo.round = StrSave("-");
16132         }
16133         if (first.twoMachinesColor[0] == 'w') {
16134             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16135             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16136         } else {
16137             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16138             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16139         }
16140         gameInfo.timeControl = TimeControlTagValue();
16141         break;
16142
16143       case EditGame:
16144         gameInfo.event = StrSave("Edited game");
16145         gameInfo.site = StrSave(HostName());
16146         gameInfo.date = PGNDate();
16147         gameInfo.round = StrSave("-");
16148         gameInfo.white = StrSave("-");
16149         gameInfo.black = StrSave("-");
16150         gameInfo.result = r;
16151         gameInfo.resultDetails = p;
16152         break;
16153
16154       case EditPosition:
16155         gameInfo.event = StrSave("Edited position");
16156         gameInfo.site = StrSave(HostName());
16157         gameInfo.date = PGNDate();
16158         gameInfo.round = StrSave("-");
16159         gameInfo.white = StrSave("-");
16160         gameInfo.black = StrSave("-");
16161         break;
16162
16163       case IcsPlayingWhite:
16164       case IcsPlayingBlack:
16165       case IcsObserving:
16166       case IcsExamining:
16167         break;
16168
16169       case PlayFromGameFile:
16170         gameInfo.event = StrSave("Game from non-PGN file");
16171         gameInfo.site = StrSave(HostName());
16172         gameInfo.date = PGNDate();
16173         gameInfo.round = StrSave("-");
16174         gameInfo.white = StrSave("?");
16175         gameInfo.black = StrSave("?");
16176         break;
16177
16178       default:
16179         break;
16180     }
16181 }
16182
16183 void
16184 ReplaceComment (int index, char *text)
16185 {
16186     int len;
16187     char *p;
16188     float score;
16189
16190     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16191        pvInfoList[index-1].depth == len &&
16192        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16193        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16194     while (*text == '\n') text++;
16195     len = strlen(text);
16196     while (len > 0 && text[len - 1] == '\n') len--;
16197
16198     if (commentList[index] != NULL)
16199       free(commentList[index]);
16200
16201     if (len == 0) {
16202         commentList[index] = NULL;
16203         return;
16204     }
16205   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16206       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16207       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16208     commentList[index] = (char *) malloc(len + 2);
16209     strncpy(commentList[index], text, len);
16210     commentList[index][len] = '\n';
16211     commentList[index][len + 1] = NULLCHAR;
16212   } else {
16213     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16214     char *p;
16215     commentList[index] = (char *) malloc(len + 7);
16216     safeStrCpy(commentList[index], "{\n", 3);
16217     safeStrCpy(commentList[index]+2, text, len+1);
16218     commentList[index][len+2] = NULLCHAR;
16219     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16220     strcat(commentList[index], "\n}\n");
16221   }
16222 }
16223
16224 void
16225 CrushCRs (char *text)
16226 {
16227   char *p = text;
16228   char *q = text;
16229   char ch;
16230
16231   do {
16232     ch = *p++;
16233     if (ch == '\r') continue;
16234     *q++ = ch;
16235   } while (ch != '\0');
16236 }
16237
16238 void
16239 AppendComment (int index, char *text, Boolean addBraces)
16240 /* addBraces  tells if we should add {} */
16241 {
16242     int oldlen, len;
16243     char *old;
16244
16245 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16246     if(addBraces == 3) addBraces = 0; else // force appending literally
16247     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16248
16249     CrushCRs(text);
16250     while (*text == '\n') text++;
16251     len = strlen(text);
16252     while (len > 0 && text[len - 1] == '\n') len--;
16253     text[len] = NULLCHAR;
16254
16255     if (len == 0) return;
16256
16257     if (commentList[index] != NULL) {
16258       Boolean addClosingBrace = addBraces;
16259         old = commentList[index];
16260         oldlen = strlen(old);
16261         while(commentList[index][oldlen-1] ==  '\n')
16262           commentList[index][--oldlen] = NULLCHAR;
16263         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16264         safeStrCpy(commentList[index], old, oldlen + len + 6);
16265         free(old);
16266         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16267         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16268           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16269           while (*text == '\n') { text++; len--; }
16270           commentList[index][--oldlen] = NULLCHAR;
16271       }
16272         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16273         else          strcat(commentList[index], "\n");
16274         strcat(commentList[index], text);
16275         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16276         else          strcat(commentList[index], "\n");
16277     } else {
16278         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16279         if(addBraces)
16280           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16281         else commentList[index][0] = NULLCHAR;
16282         strcat(commentList[index], text);
16283         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16284         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16285     }
16286 }
16287
16288 static char *
16289 FindStr (char * text, char * sub_text)
16290 {
16291     char * result = strstr( text, sub_text );
16292
16293     if( result != NULL ) {
16294         result += strlen( sub_text );
16295     }
16296
16297     return result;
16298 }
16299
16300 /* [AS] Try to extract PV info from PGN comment */
16301 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16302 char *
16303 GetInfoFromComment (int index, char * text)
16304 {
16305     char * sep = text, *p;
16306
16307     if( text != NULL && index > 0 ) {
16308         int score = 0;
16309         int depth = 0;
16310         int time = -1, sec = 0, deci;
16311         char * s_eval = FindStr( text, "[%eval " );
16312         char * s_emt = FindStr( text, "[%emt " );
16313 #if 0
16314         if( s_eval != NULL || s_emt != NULL ) {
16315 #else
16316         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16317 #endif
16318             /* New style */
16319             char delim;
16320
16321             if( s_eval != NULL ) {
16322                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16323                     return text;
16324                 }
16325
16326                 if( delim != ']' ) {
16327                     return text;
16328                 }
16329             }
16330
16331             if( s_emt != NULL ) {
16332             }
16333                 return text;
16334         }
16335         else {
16336             /* We expect something like: [+|-]nnn.nn/dd */
16337             int score_lo = 0;
16338
16339             if(*text != '{') return text; // [HGM] braces: must be normal comment
16340
16341             sep = strchr( text, '/' );
16342             if( sep == NULL || sep < (text+4) ) {
16343                 return text;
16344             }
16345
16346             p = text;
16347             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16348             if(p[1] == '(') { // comment starts with PV
16349                p = strchr(p, ')'); // locate end of PV
16350                if(p == NULL || sep < p+5) return text;
16351                // at this point we have something like "{(.*) +0.23/6 ..."
16352                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16353                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16354                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16355             }
16356             time = -1; sec = -1; deci = -1;
16357             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16358                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16359                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16360                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16361                 return text;
16362             }
16363
16364             if( score_lo < 0 || score_lo >= 100 ) {
16365                 return text;
16366             }
16367
16368             if(sec >= 0) time = 600*time + 10*sec; else
16369             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16370
16371             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16372
16373             /* [HGM] PV time: now locate end of PV info */
16374             while( *++sep >= '0' && *sep <= '9'); // strip depth
16375             if(time >= 0)
16376             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16377             if(sec >= 0)
16378             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16379             if(deci >= 0)
16380             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16381             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16382         }
16383
16384         if( depth <= 0 ) {
16385             return text;
16386         }
16387
16388         if( time < 0 ) {
16389             time = -1;
16390         }
16391
16392         pvInfoList[index-1].depth = depth;
16393         pvInfoList[index-1].score = score;
16394         pvInfoList[index-1].time  = 10*time; // centi-sec
16395         if(*sep == '}') *sep = 0; else *--sep = '{';
16396         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16397     }
16398     return sep;
16399 }
16400
16401 void
16402 SendToProgram (char *message, ChessProgramState *cps)
16403 {
16404     int count, outCount, error;
16405     char buf[MSG_SIZ];
16406
16407     if (cps->pr == NoProc) return;
16408     Attention(cps);
16409
16410     if (appData.debugMode) {
16411         TimeMark now;
16412         GetTimeMark(&now);
16413         fprintf(debugFP, "%ld >%-6s: %s",
16414                 SubtractTimeMarks(&now, &programStartTime),
16415                 cps->which, message);
16416         if(serverFP)
16417             fprintf(serverFP, "%ld >%-6s: %s",
16418                 SubtractTimeMarks(&now, &programStartTime),
16419                 cps->which, message), fflush(serverFP);
16420     }
16421
16422     count = strlen(message);
16423     outCount = OutputToProcess(cps->pr, message, count, &error);
16424     if (outCount < count && !exiting
16425                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16426       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16427       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16428         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16429             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16430                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16431                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16432                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16433             } else {
16434                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16435                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16436                 gameInfo.result = res;
16437             }
16438             gameInfo.resultDetails = StrSave(buf);
16439         }
16440         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16441         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16442     }
16443 }
16444
16445 void
16446 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16447 {
16448     char *end_str;
16449     char buf[MSG_SIZ];
16450     ChessProgramState *cps = (ChessProgramState *)closure;
16451
16452     if (isr != cps->isr) return; /* Killed intentionally */
16453     if (count <= 0) {
16454         if (count == 0) {
16455             RemoveInputSource(cps->isr);
16456             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16457                     _(cps->which), cps->program);
16458             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16459             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16460                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16461                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16462                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16463                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16464                 } else {
16465                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16466                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16467                     gameInfo.result = res;
16468                 }
16469                 gameInfo.resultDetails = StrSave(buf);
16470             }
16471             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16472             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16473         } else {
16474             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16475                     _(cps->which), cps->program);
16476             RemoveInputSource(cps->isr);
16477
16478             /* [AS] Program is misbehaving badly... kill it */
16479             if( count == -2 ) {
16480                 DestroyChildProcess( cps->pr, 9 );
16481                 cps->pr = NoProc;
16482             }
16483
16484             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16485         }
16486         return;
16487     }
16488
16489     if ((end_str = strchr(message, '\r')) != NULL)
16490       *end_str = NULLCHAR;
16491     if ((end_str = strchr(message, '\n')) != NULL)
16492       *end_str = NULLCHAR;
16493
16494     if (appData.debugMode) {
16495         TimeMark now; int print = 1;
16496         char *quote = ""; char c; int i;
16497
16498         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16499                 char start = message[0];
16500                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16501                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16502                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16503                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16504                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16505                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16506                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16507                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16508                    sscanf(message, "hint: %c", &c)!=1 &&
16509                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16510                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16511                     print = (appData.engineComments >= 2);
16512                 }
16513                 message[0] = start; // restore original message
16514         }
16515         if(print) {
16516                 GetTimeMark(&now);
16517                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16518                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16519                         quote,
16520                         message);
16521                 if(serverFP)
16522                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16523                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16524                         quote,
16525                         message), fflush(serverFP);
16526         }
16527     }
16528
16529     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16530     if (appData.icsEngineAnalyze) {
16531         if (strstr(message, "whisper") != NULL ||
16532              strstr(message, "kibitz") != NULL ||
16533             strstr(message, "tellics") != NULL) return;
16534     }
16535
16536     HandleMachineMove(message, cps);
16537 }
16538
16539
16540 void
16541 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16542 {
16543     char buf[MSG_SIZ];
16544     int seconds;
16545
16546     if( timeControl_2 > 0 ) {
16547         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16548             tc = timeControl_2;
16549         }
16550     }
16551     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16552     inc /= cps->timeOdds;
16553     st  /= cps->timeOdds;
16554
16555     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16556
16557     if (st > 0) {
16558       /* Set exact time per move, normally using st command */
16559       if (cps->stKludge) {
16560         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16561         seconds = st % 60;
16562         if (seconds == 0) {
16563           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16564         } else {
16565           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16566         }
16567       } else {
16568         snprintf(buf, MSG_SIZ, "st %d\n", st);
16569       }
16570     } else {
16571       /* Set conventional or incremental time control, using level command */
16572       if (seconds == 0) {
16573         /* Note old gnuchess bug -- minutes:seconds used to not work.
16574            Fixed in later versions, but still avoid :seconds
16575            when seconds is 0. */
16576         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16577       } else {
16578         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16579                  seconds, inc/1000.);
16580       }
16581     }
16582     SendToProgram(buf, cps);
16583
16584     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16585     /* Orthogonally, limit search to given depth */
16586     if (sd > 0) {
16587       if (cps->sdKludge) {
16588         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16589       } else {
16590         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16591       }
16592       SendToProgram(buf, cps);
16593     }
16594
16595     if(cps->nps >= 0) { /* [HGM] nps */
16596         if(cps->supportsNPS == FALSE)
16597           cps->nps = -1; // don't use if engine explicitly says not supported!
16598         else {
16599           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16600           SendToProgram(buf, cps);
16601         }
16602     }
16603 }
16604
16605 ChessProgramState *
16606 WhitePlayer ()
16607 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16608 {
16609     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16610        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16611         return &second;
16612     return &first;
16613 }
16614
16615 void
16616 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16617 {
16618     char message[MSG_SIZ];
16619     long time, otime;
16620
16621     /* Note: this routine must be called when the clocks are stopped
16622        or when they have *just* been set or switched; otherwise
16623        it will be off by the time since the current tick started.
16624     */
16625     if (machineWhite) {
16626         time = whiteTimeRemaining / 10;
16627         otime = blackTimeRemaining / 10;
16628     } else {
16629         time = blackTimeRemaining / 10;
16630         otime = whiteTimeRemaining / 10;
16631     }
16632     /* [HGM] translate opponent's time by time-odds factor */
16633     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16634
16635     if (time <= 0) time = 1;
16636     if (otime <= 0) otime = 1;
16637
16638     snprintf(message, MSG_SIZ, "time %ld\n", time);
16639     SendToProgram(message, cps);
16640
16641     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16642     SendToProgram(message, cps);
16643 }
16644
16645 char *
16646 EngineDefinedVariant (ChessProgramState *cps, int n)
16647 {   // return name of n-th unknown variant that engine supports
16648     static char buf[MSG_SIZ];
16649     char *p, *s = cps->variants;
16650     if(!s) return NULL;
16651     do { // parse string from variants feature
16652       VariantClass v;
16653         p = strchr(s, ',');
16654         if(p) *p = NULLCHAR;
16655       v = StringToVariant(s);
16656       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16657         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16658             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16659                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16660                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16661                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16662             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16663         }
16664         if(p) *p++ = ',';
16665         if(n < 0) return buf;
16666     } while(s = p);
16667     return NULL;
16668 }
16669
16670 int
16671 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16672 {
16673   char buf[MSG_SIZ];
16674   int len = strlen(name);
16675   int val;
16676
16677   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16678     (*p) += len + 1;
16679     sscanf(*p, "%d", &val);
16680     *loc = (val != 0);
16681     while (**p && **p != ' ')
16682       (*p)++;
16683     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16684     SendToProgram(buf, cps);
16685     return TRUE;
16686   }
16687   return FALSE;
16688 }
16689
16690 int
16691 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16692 {
16693   char buf[MSG_SIZ];
16694   int len = strlen(name);
16695   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16696     (*p) += len + 1;
16697     sscanf(*p, "%d", loc);
16698     while (**p && **p != ' ') (*p)++;
16699     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16700     SendToProgram(buf, cps);
16701     return TRUE;
16702   }
16703   return FALSE;
16704 }
16705
16706 int
16707 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16708 {
16709   char buf[MSG_SIZ];
16710   int len = strlen(name);
16711   if (strncmp((*p), name, len) == 0
16712       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16713     (*p) += len + 2;
16714     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16715     sscanf(*p, "%[^\"]", *loc);
16716     while (**p && **p != '\"') (*p)++;
16717     if (**p == '\"') (*p)++;
16718     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16719     SendToProgram(buf, cps);
16720     return TRUE;
16721   }
16722   return FALSE;
16723 }
16724
16725 int
16726 ParseOption (Option *opt, ChessProgramState *cps)
16727 // [HGM] options: process the string that defines an engine option, and determine
16728 // name, type, default value, and allowed value range
16729 {
16730         char *p, *q, buf[MSG_SIZ];
16731         int n, min = (-1)<<31, max = 1<<31, def;
16732
16733         if(p = strstr(opt->name, " -spin ")) {
16734             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16735             if(max < min) max = min; // enforce consistency
16736             if(def < min) def = min;
16737             if(def > max) def = max;
16738             opt->value = def;
16739             opt->min = min;
16740             opt->max = max;
16741             opt->type = Spin;
16742         } else if((p = strstr(opt->name, " -slider "))) {
16743             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16744             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16745             if(max < min) max = min; // enforce consistency
16746             if(def < min) def = min;
16747             if(def > max) def = max;
16748             opt->value = def;
16749             opt->min = min;
16750             opt->max = max;
16751             opt->type = Spin; // Slider;
16752         } else if((p = strstr(opt->name, " -string "))) {
16753             opt->textValue = p+9;
16754             opt->type = TextBox;
16755         } else if((p = strstr(opt->name, " -file "))) {
16756             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16757             opt->textValue = p+7;
16758             opt->type = FileName; // FileName;
16759         } else if((p = strstr(opt->name, " -path "))) {
16760             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16761             opt->textValue = p+7;
16762             opt->type = PathName; // PathName;
16763         } else if(p = strstr(opt->name, " -check ")) {
16764             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16765             opt->value = (def != 0);
16766             opt->type = CheckBox;
16767         } else if(p = strstr(opt->name, " -combo ")) {
16768             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16769             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16770             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16771             opt->value = n = 0;
16772             while(q = StrStr(q, " /// ")) {
16773                 n++; *q = 0;    // count choices, and null-terminate each of them
16774                 q += 5;
16775                 if(*q == '*') { // remember default, which is marked with * prefix
16776                     q++;
16777                     opt->value = n;
16778                 }
16779                 cps->comboList[cps->comboCnt++] = q;
16780             }
16781             cps->comboList[cps->comboCnt++] = NULL;
16782             opt->max = n + 1;
16783             opt->type = ComboBox;
16784         } else if(p = strstr(opt->name, " -button")) {
16785             opt->type = Button;
16786         } else if(p = strstr(opt->name, " -save")) {
16787             opt->type = SaveButton;
16788         } else return FALSE;
16789         *p = 0; // terminate option name
16790         // now look if the command-line options define a setting for this engine option.
16791         if(cps->optionSettings && cps->optionSettings[0])
16792             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16793         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16794           snprintf(buf, MSG_SIZ, "option %s", p);
16795                 if(p = strstr(buf, ",")) *p = 0;
16796                 if(q = strchr(buf, '=')) switch(opt->type) {
16797                     case ComboBox:
16798                         for(n=0; n<opt->max; n++)
16799                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16800                         break;
16801                     case TextBox:
16802                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16803                         break;
16804                     case Spin:
16805                     case CheckBox:
16806                         opt->value = atoi(q+1);
16807                     default:
16808                         break;
16809                 }
16810                 strcat(buf, "\n");
16811                 SendToProgram(buf, cps);
16812         }
16813         return TRUE;
16814 }
16815
16816 void
16817 FeatureDone (ChessProgramState *cps, int val)
16818 {
16819   DelayedEventCallback cb = GetDelayedEvent();
16820   if ((cb == InitBackEnd3 && cps == &first) ||
16821       (cb == SettingsMenuIfReady && cps == &second) ||
16822       (cb == LoadEngine) ||
16823       (cb == TwoMachinesEventIfReady)) {
16824     CancelDelayedEvent();
16825     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16826   }
16827   cps->initDone = val;
16828   if(val) cps->reload = FALSE;
16829 }
16830
16831 /* Parse feature command from engine */
16832 void
16833 ParseFeatures (char *args, ChessProgramState *cps)
16834 {
16835   char *p = args;
16836   char *q = NULL;
16837   int val;
16838   char buf[MSG_SIZ];
16839
16840   for (;;) {
16841     while (*p == ' ') p++;
16842     if (*p == NULLCHAR) return;
16843
16844     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16845     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16846     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16847     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16848     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16849     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16850     if (BoolFeature(&p, "reuse", &val, cps)) {
16851       /* Engine can disable reuse, but can't enable it if user said no */
16852       if (!val) cps->reuse = FALSE;
16853       continue;
16854     }
16855     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16856     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16857       if (gameMode == TwoMachinesPlay) {
16858         DisplayTwoMachinesTitle();
16859       } else {
16860         DisplayTitle("");
16861       }
16862       continue;
16863     }
16864     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16865     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16866     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16867     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16868     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16869     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16870     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16871     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16872     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16873     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16874     if (IntFeature(&p, "done", &val, cps)) {
16875       FeatureDone(cps, val);
16876       continue;
16877     }
16878     /* Added by Tord: */
16879     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16880     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16881     /* End of additions by Tord */
16882
16883     /* [HGM] added features: */
16884     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16885     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16886     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16887     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16888     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16889     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16890     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16891     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16892         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16893         FREE(cps->option[cps->nrOptions].name);
16894         cps->option[cps->nrOptions].name = q; q = NULL;
16895         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16896           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16897             SendToProgram(buf, cps);
16898             continue;
16899         }
16900         if(cps->nrOptions >= MAX_OPTIONS) {
16901             cps->nrOptions--;
16902             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16903             DisplayError(buf, 0);
16904         }
16905         continue;
16906     }
16907     /* End of additions by HGM */
16908
16909     /* unknown feature: complain and skip */
16910     q = p;
16911     while (*q && *q != '=') q++;
16912     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16913     SendToProgram(buf, cps);
16914     p = q;
16915     if (*p == '=') {
16916       p++;
16917       if (*p == '\"') {
16918         p++;
16919         while (*p && *p != '\"') p++;
16920         if (*p == '\"') p++;
16921       } else {
16922         while (*p && *p != ' ') p++;
16923       }
16924     }
16925   }
16926
16927 }
16928
16929 void
16930 PeriodicUpdatesEvent (int newState)
16931 {
16932     if (newState == appData.periodicUpdates)
16933       return;
16934
16935     appData.periodicUpdates=newState;
16936
16937     /* Display type changes, so update it now */
16938 //    DisplayAnalysis();
16939
16940     /* Get the ball rolling again... */
16941     if (newState) {
16942         AnalysisPeriodicEvent(1);
16943         StartAnalysisClock();
16944     }
16945 }
16946
16947 void
16948 PonderNextMoveEvent (int newState)
16949 {
16950     if (newState == appData.ponderNextMove) return;
16951     if (gameMode == EditPosition) EditPositionDone(TRUE);
16952     if (newState) {
16953         SendToProgram("hard\n", &first);
16954         if (gameMode == TwoMachinesPlay) {
16955             SendToProgram("hard\n", &second);
16956         }
16957     } else {
16958         SendToProgram("easy\n", &first);
16959         thinkOutput[0] = NULLCHAR;
16960         if (gameMode == TwoMachinesPlay) {
16961             SendToProgram("easy\n", &second);
16962         }
16963     }
16964     appData.ponderNextMove = newState;
16965 }
16966
16967 void
16968 NewSettingEvent (int option, int *feature, char *command, int value)
16969 {
16970     char buf[MSG_SIZ];
16971
16972     if (gameMode == EditPosition) EditPositionDone(TRUE);
16973     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16974     if(feature == NULL || *feature) SendToProgram(buf, &first);
16975     if (gameMode == TwoMachinesPlay) {
16976         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16977     }
16978 }
16979
16980 void
16981 ShowThinkingEvent ()
16982 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16983 {
16984     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16985     int newState = appData.showThinking
16986         // [HGM] thinking: other features now need thinking output as well
16987         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16988
16989     if (oldState == newState) return;
16990     oldState = newState;
16991     if (gameMode == EditPosition) EditPositionDone(TRUE);
16992     if (oldState) {
16993         SendToProgram("post\n", &first);
16994         if (gameMode == TwoMachinesPlay) {
16995             SendToProgram("post\n", &second);
16996         }
16997     } else {
16998         SendToProgram("nopost\n", &first);
16999         thinkOutput[0] = NULLCHAR;
17000         if (gameMode == TwoMachinesPlay) {
17001             SendToProgram("nopost\n", &second);
17002         }
17003     }
17004 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17005 }
17006
17007 void
17008 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17009 {
17010   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17011   if (pr == NoProc) return;
17012   AskQuestion(title, question, replyPrefix, pr);
17013 }
17014
17015 void
17016 TypeInEvent (char firstChar)
17017 {
17018     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17019         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17020         gameMode == AnalyzeMode || gameMode == EditGame ||
17021         gameMode == EditPosition || gameMode == IcsExamining ||
17022         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17023         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17024                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17025                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17026         gameMode == Training) PopUpMoveDialog(firstChar);
17027 }
17028
17029 void
17030 TypeInDoneEvent (char *move)
17031 {
17032         Board board;
17033         int n, fromX, fromY, toX, toY;
17034         char promoChar;
17035         ChessMove moveType;
17036
17037         // [HGM] FENedit
17038         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17039                 EditPositionPasteFEN(move);
17040                 return;
17041         }
17042         // [HGM] movenum: allow move number to be typed in any mode
17043         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17044           ToNrEvent(2*n-1);
17045           return;
17046         }
17047         // undocumented kludge: allow command-line option to be typed in!
17048         // (potentially fatal, and does not implement the effect of the option.)
17049         // should only be used for options that are values on which future decisions will be made,
17050         // and definitely not on options that would be used during initialization.
17051         if(strstr(move, "!!! -") == move) {
17052             ParseArgsFromString(move+4);
17053             return;
17054         }
17055
17056       if (gameMode != EditGame && currentMove != forwardMostMove &&
17057         gameMode != Training) {
17058         DisplayMoveError(_("Displayed move is not current"));
17059       } else {
17060         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17061           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17062         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17063         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17064           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17065           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17066         } else {
17067           DisplayMoveError(_("Could not parse move"));
17068         }
17069       }
17070 }
17071
17072 void
17073 DisplayMove (int moveNumber)
17074 {
17075     char message[MSG_SIZ];
17076     char res[MSG_SIZ];
17077     char cpThinkOutput[MSG_SIZ];
17078
17079     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17080
17081     if (moveNumber == forwardMostMove - 1 ||
17082         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17083
17084         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17085
17086         if (strchr(cpThinkOutput, '\n')) {
17087             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17088         }
17089     } else {
17090         *cpThinkOutput = NULLCHAR;
17091     }
17092
17093     /* [AS] Hide thinking from human user */
17094     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17095         *cpThinkOutput = NULLCHAR;
17096         if( thinkOutput[0] != NULLCHAR ) {
17097             int i;
17098
17099             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17100                 cpThinkOutput[i] = '.';
17101             }
17102             cpThinkOutput[i] = NULLCHAR;
17103             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17104         }
17105     }
17106
17107     if (moveNumber == forwardMostMove - 1 &&
17108         gameInfo.resultDetails != NULL) {
17109         if (gameInfo.resultDetails[0] == NULLCHAR) {
17110           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17111         } else {
17112           snprintf(res, MSG_SIZ, " {%s} %s",
17113                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17114         }
17115     } else {
17116         res[0] = NULLCHAR;
17117     }
17118
17119     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17120         DisplayMessage(res, cpThinkOutput);
17121     } else {
17122       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17123                 WhiteOnMove(moveNumber) ? " " : ".. ",
17124                 parseList[moveNumber], res);
17125         DisplayMessage(message, cpThinkOutput);
17126     }
17127 }
17128
17129 void
17130 DisplayComment (int moveNumber, char *text)
17131 {
17132     char title[MSG_SIZ];
17133
17134     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17135       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17136     } else {
17137       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17138               WhiteOnMove(moveNumber) ? " " : ".. ",
17139               parseList[moveNumber]);
17140     }
17141     if (text != NULL && (appData.autoDisplayComment || commentUp))
17142         CommentPopUp(title, text);
17143 }
17144
17145 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17146  * might be busy thinking or pondering.  It can be omitted if your
17147  * gnuchess is configured to stop thinking immediately on any user
17148  * input.  However, that gnuchess feature depends on the FIONREAD
17149  * ioctl, which does not work properly on some flavors of Unix.
17150  */
17151 void
17152 Attention (ChessProgramState *cps)
17153 {
17154 #if ATTENTION
17155     if (!cps->useSigint) return;
17156     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17157     switch (gameMode) {
17158       case MachinePlaysWhite:
17159       case MachinePlaysBlack:
17160       case TwoMachinesPlay:
17161       case IcsPlayingWhite:
17162       case IcsPlayingBlack:
17163       case AnalyzeMode:
17164       case AnalyzeFile:
17165         /* Skip if we know it isn't thinking */
17166         if (!cps->maybeThinking) return;
17167         if (appData.debugMode)
17168           fprintf(debugFP, "Interrupting %s\n", cps->which);
17169         InterruptChildProcess(cps->pr);
17170         cps->maybeThinking = FALSE;
17171         break;
17172       default:
17173         break;
17174     }
17175 #endif /*ATTENTION*/
17176 }
17177
17178 int
17179 CheckFlags ()
17180 {
17181     if (whiteTimeRemaining <= 0) {
17182         if (!whiteFlag) {
17183             whiteFlag = TRUE;
17184             if (appData.icsActive) {
17185                 if (appData.autoCallFlag &&
17186                     gameMode == IcsPlayingBlack && !blackFlag) {
17187                   SendToICS(ics_prefix);
17188                   SendToICS("flag\n");
17189                 }
17190             } else {
17191                 if (blackFlag) {
17192                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17193                 } else {
17194                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17195                     if (appData.autoCallFlag) {
17196                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17197                         return TRUE;
17198                     }
17199                 }
17200             }
17201         }
17202     }
17203     if (blackTimeRemaining <= 0) {
17204         if (!blackFlag) {
17205             blackFlag = TRUE;
17206             if (appData.icsActive) {
17207                 if (appData.autoCallFlag &&
17208                     gameMode == IcsPlayingWhite && !whiteFlag) {
17209                   SendToICS(ics_prefix);
17210                   SendToICS("flag\n");
17211                 }
17212             } else {
17213                 if (whiteFlag) {
17214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17215                 } else {
17216                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17217                     if (appData.autoCallFlag) {
17218                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17219                         return TRUE;
17220                     }
17221                 }
17222             }
17223         }
17224     }
17225     return FALSE;
17226 }
17227
17228 void
17229 CheckTimeControl ()
17230 {
17231     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17232         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17233
17234     /*
17235      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17236      */
17237     if ( !WhiteOnMove(forwardMostMove) ) {
17238         /* White made time control */
17239         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17240         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17241         /* [HGM] time odds: correct new time quota for time odds! */
17242                                             / WhitePlayer()->timeOdds;
17243         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17244     } else {
17245         lastBlack -= blackTimeRemaining;
17246         /* Black made time control */
17247         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17248                                             / WhitePlayer()->other->timeOdds;
17249         lastWhite = whiteTimeRemaining;
17250     }
17251 }
17252
17253 void
17254 DisplayBothClocks ()
17255 {
17256     int wom = gameMode == EditPosition ?
17257       !blackPlaysFirst : WhiteOnMove(currentMove);
17258     DisplayWhiteClock(whiteTimeRemaining, wom);
17259     DisplayBlackClock(blackTimeRemaining, !wom);
17260 }
17261
17262
17263 /* Timekeeping seems to be a portability nightmare.  I think everyone
17264    has ftime(), but I'm really not sure, so I'm including some ifdefs
17265    to use other calls if you don't.  Clocks will be less accurate if
17266    you have neither ftime nor gettimeofday.
17267 */
17268
17269 /* VS 2008 requires the #include outside of the function */
17270 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17271 #include <sys/timeb.h>
17272 #endif
17273
17274 /* Get the current time as a TimeMark */
17275 void
17276 GetTimeMark (TimeMark *tm)
17277 {
17278 #if HAVE_GETTIMEOFDAY
17279
17280     struct timeval timeVal;
17281     struct timezone timeZone;
17282
17283     gettimeofday(&timeVal, &timeZone);
17284     tm->sec = (long) timeVal.tv_sec;
17285     tm->ms = (int) (timeVal.tv_usec / 1000L);
17286
17287 #else /*!HAVE_GETTIMEOFDAY*/
17288 #if HAVE_FTIME
17289
17290 // include <sys/timeb.h> / moved to just above start of function
17291     struct timeb timeB;
17292
17293     ftime(&timeB);
17294     tm->sec = (long) timeB.time;
17295     tm->ms = (int) timeB.millitm;
17296
17297 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17298     tm->sec = (long) time(NULL);
17299     tm->ms = 0;
17300 #endif
17301 #endif
17302 }
17303
17304 /* Return the difference in milliseconds between two
17305    time marks.  We assume the difference will fit in a long!
17306 */
17307 long
17308 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17309 {
17310     return 1000L*(tm2->sec - tm1->sec) +
17311            (long) (tm2->ms - tm1->ms);
17312 }
17313
17314
17315 /*
17316  * Code to manage the game clocks.
17317  *
17318  * In tournament play, black starts the clock and then white makes a move.
17319  * We give the human user a slight advantage if he is playing white---the
17320  * clocks don't run until he makes his first move, so it takes zero time.
17321  * Also, we don't account for network lag, so we could get out of sync
17322  * with GNU Chess's clock -- but then, referees are always right.
17323  */
17324
17325 static TimeMark tickStartTM;
17326 static long intendedTickLength;
17327
17328 long
17329 NextTickLength (long timeRemaining)
17330 {
17331     long nominalTickLength, nextTickLength;
17332
17333     if (timeRemaining > 0L && timeRemaining <= 10000L)
17334       nominalTickLength = 100L;
17335     else
17336       nominalTickLength = 1000L;
17337     nextTickLength = timeRemaining % nominalTickLength;
17338     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17339
17340     return nextTickLength;
17341 }
17342
17343 /* Adjust clock one minute up or down */
17344 void
17345 AdjustClock (Boolean which, int dir)
17346 {
17347     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17348     if(which) blackTimeRemaining += 60000*dir;
17349     else      whiteTimeRemaining += 60000*dir;
17350     DisplayBothClocks();
17351     adjustedClock = TRUE;
17352 }
17353
17354 /* Stop clocks and reset to a fresh time control */
17355 void
17356 ResetClocks ()
17357 {
17358     (void) StopClockTimer();
17359     if (appData.icsActive) {
17360         whiteTimeRemaining = blackTimeRemaining = 0;
17361     } else if (searchTime) {
17362         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17363         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17364     } else { /* [HGM] correct new time quote for time odds */
17365         whiteTC = blackTC = fullTimeControlString;
17366         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17367         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17368     }
17369     if (whiteFlag || blackFlag) {
17370         DisplayTitle("");
17371         whiteFlag = blackFlag = FALSE;
17372     }
17373     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17374     DisplayBothClocks();
17375     adjustedClock = FALSE;
17376 }
17377
17378 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17379
17380 /* Decrement running clock by amount of time that has passed */
17381 void
17382 DecrementClocks ()
17383 {
17384     long timeRemaining;
17385     long lastTickLength, fudge;
17386     TimeMark now;
17387
17388     if (!appData.clockMode) return;
17389     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17390
17391     GetTimeMark(&now);
17392
17393     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17394
17395     /* Fudge if we woke up a little too soon */
17396     fudge = intendedTickLength - lastTickLength;
17397     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17398
17399     if (WhiteOnMove(forwardMostMove)) {
17400         if(whiteNPS >= 0) lastTickLength = 0;
17401         timeRemaining = whiteTimeRemaining -= lastTickLength;
17402         if(timeRemaining < 0 && !appData.icsActive) {
17403             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17404             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17405                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17406                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17407             }
17408         }
17409         DisplayWhiteClock(whiteTimeRemaining - fudge,
17410                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17411     } else {
17412         if(blackNPS >= 0) lastTickLength = 0;
17413         timeRemaining = blackTimeRemaining -= lastTickLength;
17414         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17415             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17416             if(suddenDeath) {
17417                 blackStartMove = forwardMostMove;
17418                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17419             }
17420         }
17421         DisplayBlackClock(blackTimeRemaining - fudge,
17422                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17423     }
17424     if (CheckFlags()) return;
17425
17426     if(twoBoards) { // count down secondary board's clocks as well
17427         activePartnerTime -= lastTickLength;
17428         partnerUp = 1;
17429         if(activePartner == 'W')
17430             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17431         else
17432             DisplayBlackClock(activePartnerTime, TRUE);
17433         partnerUp = 0;
17434     }
17435
17436     tickStartTM = now;
17437     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17438     StartClockTimer(intendedTickLength);
17439
17440     /* if the time remaining has fallen below the alarm threshold, sound the
17441      * alarm. if the alarm has sounded and (due to a takeback or time control
17442      * with increment) the time remaining has increased to a level above the
17443      * threshold, reset the alarm so it can sound again.
17444      */
17445
17446     if (appData.icsActive && appData.icsAlarm) {
17447
17448         /* make sure we are dealing with the user's clock */
17449         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17450                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17451            )) return;
17452
17453         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17454             alarmSounded = FALSE;
17455         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17456             PlayAlarmSound();
17457             alarmSounded = TRUE;
17458         }
17459     }
17460 }
17461
17462
17463 /* A player has just moved, so stop the previously running
17464    clock and (if in clock mode) start the other one.
17465    We redisplay both clocks in case we're in ICS mode, because
17466    ICS gives us an update to both clocks after every move.
17467    Note that this routine is called *after* forwardMostMove
17468    is updated, so the last fractional tick must be subtracted
17469    from the color that is *not* on move now.
17470 */
17471 void
17472 SwitchClocks (int newMoveNr)
17473 {
17474     long lastTickLength;
17475     TimeMark now;
17476     int flagged = FALSE;
17477
17478     GetTimeMark(&now);
17479
17480     if (StopClockTimer() && appData.clockMode) {
17481         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17482         if (!WhiteOnMove(forwardMostMove)) {
17483             if(blackNPS >= 0) lastTickLength = 0;
17484             blackTimeRemaining -= lastTickLength;
17485            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17486 //         if(pvInfoList[forwardMostMove].time == -1)
17487                  pvInfoList[forwardMostMove].time =               // use GUI time
17488                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17489         } else {
17490            if(whiteNPS >= 0) lastTickLength = 0;
17491            whiteTimeRemaining -= lastTickLength;
17492            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17493 //         if(pvInfoList[forwardMostMove].time == -1)
17494                  pvInfoList[forwardMostMove].time =
17495                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17496         }
17497         flagged = CheckFlags();
17498     }
17499     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17500     CheckTimeControl();
17501
17502     if (flagged || !appData.clockMode) return;
17503
17504     switch (gameMode) {
17505       case MachinePlaysBlack:
17506       case MachinePlaysWhite:
17507       case BeginningOfGame:
17508         if (pausing) return;
17509         break;
17510
17511       case EditGame:
17512       case PlayFromGameFile:
17513       case IcsExamining:
17514         return;
17515
17516       default:
17517         break;
17518     }
17519
17520     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17521         if(WhiteOnMove(forwardMostMove))
17522              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17523         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17524     }
17525
17526     tickStartTM = now;
17527     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17528       whiteTimeRemaining : blackTimeRemaining);
17529     StartClockTimer(intendedTickLength);
17530 }
17531
17532
17533 /* Stop both clocks */
17534 void
17535 StopClocks ()
17536 {
17537     long lastTickLength;
17538     TimeMark now;
17539
17540     if (!StopClockTimer()) return;
17541     if (!appData.clockMode) return;
17542
17543     GetTimeMark(&now);
17544
17545     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17546     if (WhiteOnMove(forwardMostMove)) {
17547         if(whiteNPS >= 0) lastTickLength = 0;
17548         whiteTimeRemaining -= lastTickLength;
17549         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17550     } else {
17551         if(blackNPS >= 0) lastTickLength = 0;
17552         blackTimeRemaining -= lastTickLength;
17553         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17554     }
17555     CheckFlags();
17556 }
17557
17558 /* Start clock of player on move.  Time may have been reset, so
17559    if clock is already running, stop and restart it. */
17560 void
17561 StartClocks ()
17562 {
17563     (void) StopClockTimer(); /* in case it was running already */
17564     DisplayBothClocks();
17565     if (CheckFlags()) return;
17566
17567     if (!appData.clockMode) return;
17568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17569
17570     GetTimeMark(&tickStartTM);
17571     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17572       whiteTimeRemaining : blackTimeRemaining);
17573
17574    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17575     whiteNPS = blackNPS = -1;
17576     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17577        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17578         whiteNPS = first.nps;
17579     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17580        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17581         blackNPS = first.nps;
17582     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17583         whiteNPS = second.nps;
17584     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17585         blackNPS = second.nps;
17586     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17587
17588     StartClockTimer(intendedTickLength);
17589 }
17590
17591 char *
17592 TimeString (long ms)
17593 {
17594     long second, minute, hour, day;
17595     char *sign = "";
17596     static char buf[32];
17597
17598     if (ms > 0 && ms <= 9900) {
17599       /* convert milliseconds to tenths, rounding up */
17600       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17601
17602       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17603       return buf;
17604     }
17605
17606     /* convert milliseconds to seconds, rounding up */
17607     /* use floating point to avoid strangeness of integer division
17608        with negative dividends on many machines */
17609     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17610
17611     if (second < 0) {
17612         sign = "-";
17613         second = -second;
17614     }
17615
17616     day = second / (60 * 60 * 24);
17617     second = second % (60 * 60 * 24);
17618     hour = second / (60 * 60);
17619     second = second % (60 * 60);
17620     minute = second / 60;
17621     second = second % 60;
17622
17623     if (day > 0)
17624       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17625               sign, day, hour, minute, second);
17626     else if (hour > 0)
17627       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17628     else
17629       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17630
17631     return buf;
17632 }
17633
17634
17635 /*
17636  * This is necessary because some C libraries aren't ANSI C compliant yet.
17637  */
17638 char *
17639 StrStr (char *string, char *match)
17640 {
17641     int i, length;
17642
17643     length = strlen(match);
17644
17645     for (i = strlen(string) - length; i >= 0; i--, string++)
17646       if (!strncmp(match, string, length))
17647         return string;
17648
17649     return NULL;
17650 }
17651
17652 char *
17653 StrCaseStr (char *string, char *match)
17654 {
17655     int i, j, length;
17656
17657     length = strlen(match);
17658
17659     for (i = strlen(string) - length; i >= 0; i--, string++) {
17660         for (j = 0; j < length; j++) {
17661             if (ToLower(match[j]) != ToLower(string[j]))
17662               break;
17663         }
17664         if (j == length) return string;
17665     }
17666
17667     return NULL;
17668 }
17669
17670 #ifndef _amigados
17671 int
17672 StrCaseCmp (char *s1, char *s2)
17673 {
17674     char c1, c2;
17675
17676     for (;;) {
17677         c1 = ToLower(*s1++);
17678         c2 = ToLower(*s2++);
17679         if (c1 > c2) return 1;
17680         if (c1 < c2) return -1;
17681         if (c1 == NULLCHAR) return 0;
17682     }
17683 }
17684
17685
17686 int
17687 ToLower (int c)
17688 {
17689     return isupper(c) ? tolower(c) : c;
17690 }
17691
17692
17693 int
17694 ToUpper (int c)
17695 {
17696     return islower(c) ? toupper(c) : c;
17697 }
17698 #endif /* !_amigados    */
17699
17700 char *
17701 StrSave (char *s)
17702 {
17703   char *ret;
17704
17705   if ((ret = (char *) malloc(strlen(s) + 1)))
17706     {
17707       safeStrCpy(ret, s, strlen(s)+1);
17708     }
17709   return ret;
17710 }
17711
17712 char *
17713 StrSavePtr (char *s, char **savePtr)
17714 {
17715     if (*savePtr) {
17716         free(*savePtr);
17717     }
17718     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17719       safeStrCpy(*savePtr, s, strlen(s)+1);
17720     }
17721     return(*savePtr);
17722 }
17723
17724 char *
17725 PGNDate ()
17726 {
17727     time_t clock;
17728     struct tm *tm;
17729     char buf[MSG_SIZ];
17730
17731     clock = time((time_t *)NULL);
17732     tm = localtime(&clock);
17733     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17734             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17735     return StrSave(buf);
17736 }
17737
17738
17739 char *
17740 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17741 {
17742     int i, j, fromX, fromY, toX, toY;
17743     int whiteToPlay;
17744     char buf[MSG_SIZ];
17745     char *p, *q;
17746     int emptycount;
17747     ChessSquare piece;
17748
17749     whiteToPlay = (gameMode == EditPosition) ?
17750       !blackPlaysFirst : (move % 2 == 0);
17751     p = buf;
17752
17753     /* Piece placement data */
17754     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17755         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17756         emptycount = 0;
17757         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17758             if (boards[move][i][j] == EmptySquare) {
17759                 emptycount++;
17760             } else { ChessSquare piece = boards[move][i][j];
17761                 if (emptycount > 0) {
17762                     if(emptycount<10) /* [HGM] can be >= 10 */
17763                         *p++ = '0' + emptycount;
17764                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17765                     emptycount = 0;
17766                 }
17767                 if(PieceToChar(piece) == '+') {
17768                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17769                     *p++ = '+';
17770                     piece = (ChessSquare)(CHUDEMOTED piece);
17771                 }
17772                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17773                 if(p[-1] == '~') {
17774                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17775                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17776                     *p++ = '~';
17777                 }
17778             }
17779         }
17780         if (emptycount > 0) {
17781             if(emptycount<10) /* [HGM] can be >= 10 */
17782                 *p++ = '0' + emptycount;
17783             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17784             emptycount = 0;
17785         }
17786         *p++ = '/';
17787     }
17788     *(p - 1) = ' ';
17789
17790     /* [HGM] print Crazyhouse or Shogi holdings */
17791     if( gameInfo.holdingsWidth ) {
17792         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17793         q = p;
17794         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17795             piece = boards[move][i][BOARD_WIDTH-1];
17796             if( piece != EmptySquare )
17797               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17798                   *p++ = PieceToChar(piece);
17799         }
17800         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17801             piece = boards[move][BOARD_HEIGHT-i-1][0];
17802             if( piece != EmptySquare )
17803               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17804                   *p++ = PieceToChar(piece);
17805         }
17806
17807         if( q == p ) *p++ = '-';
17808         *p++ = ']';
17809         *p++ = ' ';
17810     }
17811
17812     /* Active color */
17813     *p++ = whiteToPlay ? 'w' : 'b';
17814     *p++ = ' ';
17815
17816   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17817     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17818   } else {
17819   if(nrCastlingRights) {
17820      int handW=0, handB=0;
17821      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17822         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17823         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17824      }
17825      q = p;
17826      if(appData.fischerCastling) {
17827         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17828            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17829                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17830         } else {
17831        /* [HGM] write directly from rights */
17832            if(boards[move][CASTLING][2] != NoRights &&
17833               boards[move][CASTLING][0] != NoRights   )
17834                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17835            if(boards[move][CASTLING][2] != NoRights &&
17836               boards[move][CASTLING][1] != NoRights   )
17837                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17838         }
17839         if(handB) {
17840            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17841                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17842         } else {
17843            if(boards[move][CASTLING][5] != NoRights &&
17844               boards[move][CASTLING][3] != NoRights   )
17845                 *p++ = boards[move][CASTLING][3] + AAA;
17846            if(boards[move][CASTLING][5] != NoRights &&
17847               boards[move][CASTLING][4] != NoRights   )
17848                 *p++ = boards[move][CASTLING][4] + AAA;
17849         }
17850      } else {
17851
17852         /* [HGM] write true castling rights */
17853         if( nrCastlingRights == 6 ) {
17854             int q, k=0;
17855             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17856                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17857             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17858                  boards[move][CASTLING][2] != NoRights  );
17859             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17860                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17861                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17862                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17863             }
17864             if(q) *p++ = 'Q';
17865             k = 0;
17866             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17867                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17868             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17869                  boards[move][CASTLING][5] != NoRights  );
17870             if(handB) {
17871                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17872                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17873                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17874             }
17875             if(q) *p++ = 'q';
17876         }
17877      }
17878      if (q == p) *p++ = '-'; /* No castling rights */
17879      *p++ = ' ';
17880   }
17881
17882   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17883      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17884      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17885     /* En passant target square */
17886     if (move > backwardMostMove) {
17887         fromX = moveList[move - 1][0] - AAA;
17888         fromY = moveList[move - 1][1] - ONE;
17889         toX = moveList[move - 1][2] - AAA;
17890         toY = moveList[move - 1][3] - ONE;
17891         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17892             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17893             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17894             fromX == toX) {
17895             /* 2-square pawn move just happened */
17896             *p++ = toX + AAA;
17897             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17898         } else {
17899             *p++ = '-';
17900         }
17901     } else if(move == backwardMostMove) {
17902         // [HGM] perhaps we should always do it like this, and forget the above?
17903         if((signed char)boards[move][EP_STATUS] >= 0) {
17904             *p++ = boards[move][EP_STATUS] + AAA;
17905             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17906         } else {
17907             *p++ = '-';
17908         }
17909     } else {
17910         *p++ = '-';
17911     }
17912     *p++ = ' ';
17913   }
17914   }
17915
17916     if(moveCounts)
17917     {   int i = 0, j=move;
17918
17919         /* [HGM] find reversible plies */
17920         if (appData.debugMode) { int k;
17921             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17922             for(k=backwardMostMove; k<=forwardMostMove; k++)
17923                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17924
17925         }
17926
17927         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17928         if( j == backwardMostMove ) i += initialRulePlies;
17929         sprintf(p, "%d ", i);
17930         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17931
17932         /* Fullmove number */
17933         sprintf(p, "%d", (move / 2) + 1);
17934     } else *--p = NULLCHAR;
17935
17936     return StrSave(buf);
17937 }
17938
17939 Boolean
17940 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17941 {
17942     int i, j, k, w=0, subst=0, shuffle=0;
17943     char *p, c;
17944     int emptycount, virgin[BOARD_FILES];
17945     ChessSquare piece;
17946
17947     p = fen;
17948
17949     /* Piece placement data */
17950     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17951         j = 0;
17952         for (;;) {
17953             if (*p == '/' || *p == ' ' || *p == '[' ) {
17954                 if(j > w) w = j;
17955                 emptycount = gameInfo.boardWidth - j;
17956                 while (emptycount--)
17957                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17958                 if (*p == '/') p++;
17959                 else if(autoSize) { // we stumbled unexpectedly into end of board
17960                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17961                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17962                     }
17963                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17964                 }
17965                 break;
17966 #if(BOARD_FILES >= 10)*0
17967             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17968                 p++; emptycount=10;
17969                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17970                 while (emptycount--)
17971                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17972 #endif
17973             } else if (*p == '*') {
17974                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17975             } else if (isdigit(*p)) {
17976                 emptycount = *p++ - '0';
17977                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17978                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17979                 while (emptycount--)
17980                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17981             } else if (*p == '<') {
17982                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17983                 else if (i != 0 || !shuffle) return FALSE;
17984                 p++;
17985             } else if (shuffle && *p == '>') {
17986                 p++; // for now ignore closing shuffle range, and assume rank-end
17987             } else if (*p == '?') {
17988                 if (j >= gameInfo.boardWidth) return FALSE;
17989                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17990                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17991             } else if (*p == '+' || isalpha(*p)) {
17992                 if (j >= gameInfo.boardWidth) return FALSE;
17993                 if(*p=='+') {
17994                     piece = CharToPiece(*++p);
17995                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17996                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17997                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17998                 } else piece = CharToPiece(*p++);
17999
18000                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18001                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18002                     piece = (ChessSquare) (PROMOTED piece);
18003                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18004                     p++;
18005                 }
18006                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18007             } else {
18008                 return FALSE;
18009             }
18010         }
18011     }
18012     while (*p == '/' || *p == ' ') p++;
18013
18014     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18015
18016     /* [HGM] by default clear Crazyhouse holdings, if present */
18017     if(gameInfo.holdingsWidth) {
18018        for(i=0; i<BOARD_HEIGHT; i++) {
18019            board[i][0]             = EmptySquare; /* black holdings */
18020            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18021            board[i][1]             = (ChessSquare) 0; /* black counts */
18022            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18023        }
18024     }
18025
18026     /* [HGM] look for Crazyhouse holdings here */
18027     while(*p==' ') p++;
18028     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18029         int swap=0, wcnt=0, bcnt=0;
18030         if(*p == '[') p++;
18031         if(*p == '<') swap++, p++;
18032         if(*p == '-' ) p++; /* empty holdings */ else {
18033             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18034             /* if we would allow FEN reading to set board size, we would   */
18035             /* have to add holdings and shift the board read so far here   */
18036             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18037                 p++;
18038                 if((int) piece >= (int) BlackPawn ) {
18039                     i = (int)piece - (int)BlackPawn;
18040                     i = PieceToNumber((ChessSquare)i);
18041                     if( i >= gameInfo.holdingsSize ) return FALSE;
18042                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18043                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18044                     bcnt++;
18045                 } else {
18046                     i = (int)piece - (int)WhitePawn;
18047                     i = PieceToNumber((ChessSquare)i);
18048                     if( i >= gameInfo.holdingsSize ) return FALSE;
18049                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18050                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18051                     wcnt++;
18052                 }
18053             }
18054             if(subst) { // substitute back-rank question marks by holdings pieces
18055                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18056                     int k, m, n = bcnt + 1;
18057                     if(board[0][j] == ClearBoard) {
18058                         if(!wcnt) return FALSE;
18059                         n = rand() % wcnt;
18060                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18061                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18062                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18063                             break;
18064                         }
18065                     }
18066                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18067                         if(!bcnt) return FALSE;
18068                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18069                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18070                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18071                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18072                             break;
18073                         }
18074                     }
18075                 }
18076                 subst = 0;
18077             }
18078         }
18079         if(*p == ']') p++;
18080     }
18081
18082     if(subst) return FALSE; // substitution requested, but no holdings
18083
18084     while(*p == ' ') p++;
18085
18086     /* Active color */
18087     c = *p++;
18088     if(appData.colorNickNames) {
18089       if( c == appData.colorNickNames[0] ) c = 'w'; else
18090       if( c == appData.colorNickNames[1] ) c = 'b';
18091     }
18092     switch (c) {
18093       case 'w':
18094         *blackPlaysFirst = FALSE;
18095         break;
18096       case 'b':
18097         *blackPlaysFirst = TRUE;
18098         break;
18099       default:
18100         return FALSE;
18101     }
18102
18103     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18104     /* return the extra info in global variiables             */
18105
18106     /* set defaults in case FEN is incomplete */
18107     board[EP_STATUS] = EP_UNKNOWN;
18108     for(i=0; i<nrCastlingRights; i++ ) {
18109         board[CASTLING][i] =
18110             appData.fischerCastling ? NoRights : initialRights[i];
18111     }   /* assume possible unless obviously impossible */
18112     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18113     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18114     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18115                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18116     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18117     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18118     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18119                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18120     FENrulePlies = 0;
18121
18122     while(*p==' ') p++;
18123     if(nrCastlingRights) {
18124       int fischer = 0;
18125       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18126       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18127           /* castling indicator present, so default becomes no castlings */
18128           for(i=0; i<nrCastlingRights; i++ ) {
18129                  board[CASTLING][i] = NoRights;
18130           }
18131       }
18132       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18133              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18134              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18135              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18136         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18137
18138         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18139             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18140             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18141         }
18142         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18143             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18144         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18145                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18146         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18147                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18148         switch(c) {
18149           case'K':
18150               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18151               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18152               board[CASTLING][2] = whiteKingFile;
18153               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18154               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18155               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18156               break;
18157           case'Q':
18158               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18159               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18160               board[CASTLING][2] = whiteKingFile;
18161               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18162               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18163               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18164               break;
18165           case'k':
18166               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18167               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18168               board[CASTLING][5] = blackKingFile;
18169               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18170               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18171               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18172               break;
18173           case'q':
18174               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18175               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18176               board[CASTLING][5] = blackKingFile;
18177               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18178               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18179               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18180           case '-':
18181               break;
18182           default: /* FRC castlings */
18183               if(c >= 'a') { /* black rights */
18184                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18185                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18186                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18187                   if(i == BOARD_RGHT) break;
18188                   board[CASTLING][5] = i;
18189                   c -= AAA;
18190                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18191                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18192                   if(c > i)
18193                       board[CASTLING][3] = c;
18194                   else
18195                       board[CASTLING][4] = c;
18196               } else { /* white rights */
18197                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18198                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18199                     if(board[0][i] == WhiteKing) break;
18200                   if(i == BOARD_RGHT) break;
18201                   board[CASTLING][2] = i;
18202                   c -= AAA - 'a' + 'A';
18203                   if(board[0][c] >= WhiteKing) break;
18204                   if(c > i)
18205                       board[CASTLING][0] = c;
18206                   else
18207                       board[CASTLING][1] = c;
18208               }
18209         }
18210       }
18211       for(i=0; i<nrCastlingRights; i++)
18212         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18213       if(gameInfo.variant == VariantSChess)
18214         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18215       if(fischer && shuffle) appData.fischerCastling = TRUE;
18216     if (appData.debugMode) {
18217         fprintf(debugFP, "FEN castling rights:");
18218         for(i=0; i<nrCastlingRights; i++)
18219         fprintf(debugFP, " %d", board[CASTLING][i]);
18220         fprintf(debugFP, "\n");
18221     }
18222
18223       while(*p==' ') p++;
18224     }
18225
18226     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18227
18228     /* read e.p. field in games that know e.p. capture */
18229     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18230        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18231        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18232       if(*p=='-') {
18233         p++; board[EP_STATUS] = EP_NONE;
18234       } else {
18235          char c = *p++ - AAA;
18236
18237          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18238          if(*p >= '0' && *p <='9') p++;
18239          board[EP_STATUS] = c;
18240       }
18241     }
18242
18243
18244     if(sscanf(p, "%d", &i) == 1) {
18245         FENrulePlies = i; /* 50-move ply counter */
18246         /* (The move number is still ignored)    */
18247     }
18248
18249     return TRUE;
18250 }
18251
18252 void
18253 EditPositionPasteFEN (char *fen)
18254 {
18255   if (fen != NULL) {
18256     Board initial_position;
18257
18258     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18259       DisplayError(_("Bad FEN position in clipboard"), 0);
18260       return ;
18261     } else {
18262       int savedBlackPlaysFirst = blackPlaysFirst;
18263       EditPositionEvent();
18264       blackPlaysFirst = savedBlackPlaysFirst;
18265       CopyBoard(boards[0], initial_position);
18266       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18267       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18268       DisplayBothClocks();
18269       DrawPosition(FALSE, boards[currentMove]);
18270     }
18271   }
18272 }
18273
18274 static char cseq[12] = "\\   ";
18275
18276 Boolean
18277 set_cont_sequence (char *new_seq)
18278 {
18279     int len;
18280     Boolean ret;
18281
18282     // handle bad attempts to set the sequence
18283         if (!new_seq)
18284                 return 0; // acceptable error - no debug
18285
18286     len = strlen(new_seq);
18287     ret = (len > 0) && (len < sizeof(cseq));
18288     if (ret)
18289       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18290     else if (appData.debugMode)
18291       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18292     return ret;
18293 }
18294
18295 /*
18296     reformat a source message so words don't cross the width boundary.  internal
18297     newlines are not removed.  returns the wrapped size (no null character unless
18298     included in source message).  If dest is NULL, only calculate the size required
18299     for the dest buffer.  lp argument indicats line position upon entry, and it's
18300     passed back upon exit.
18301 */
18302 int
18303 wrap (char *dest, char *src, int count, int width, int *lp)
18304 {
18305     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18306
18307     cseq_len = strlen(cseq);
18308     old_line = line = *lp;
18309     ansi = len = clen = 0;
18310
18311     for (i=0; i < count; i++)
18312     {
18313         if (src[i] == '\033')
18314             ansi = 1;
18315
18316         // if we hit the width, back up
18317         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18318         {
18319             // store i & len in case the word is too long
18320             old_i = i, old_len = len;
18321
18322             // find the end of the last word
18323             while (i && src[i] != ' ' && src[i] != '\n')
18324             {
18325                 i--;
18326                 len--;
18327             }
18328
18329             // word too long?  restore i & len before splitting it
18330             if ((old_i-i+clen) >= width)
18331             {
18332                 i = old_i;
18333                 len = old_len;
18334             }
18335
18336             // extra space?
18337             if (i && src[i-1] == ' ')
18338                 len--;
18339
18340             if (src[i] != ' ' && src[i] != '\n')
18341             {
18342                 i--;
18343                 if (len)
18344                     len--;
18345             }
18346
18347             // now append the newline and continuation sequence
18348             if (dest)
18349                 dest[len] = '\n';
18350             len++;
18351             if (dest)
18352                 strncpy(dest+len, cseq, cseq_len);
18353             len += cseq_len;
18354             line = cseq_len;
18355             clen = cseq_len;
18356             continue;
18357         }
18358
18359         if (dest)
18360             dest[len] = src[i];
18361         len++;
18362         if (!ansi)
18363             line++;
18364         if (src[i] == '\n')
18365             line = 0;
18366         if (src[i] == 'm')
18367             ansi = 0;
18368     }
18369     if (dest && appData.debugMode)
18370     {
18371         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18372             count, width, line, len, *lp);
18373         show_bytes(debugFP, src, count);
18374         fprintf(debugFP, "\ndest: ");
18375         show_bytes(debugFP, dest, len);
18376         fprintf(debugFP, "\n");
18377     }
18378     *lp = dest ? line : old_line;
18379
18380     return len;
18381 }
18382
18383 // [HGM] vari: routines for shelving variations
18384 Boolean modeRestore = FALSE;
18385
18386 void
18387 PushInner (int firstMove, int lastMove)
18388 {
18389         int i, j, nrMoves = lastMove - firstMove;
18390
18391         // push current tail of game on stack
18392         savedResult[storedGames] = gameInfo.result;
18393         savedDetails[storedGames] = gameInfo.resultDetails;
18394         gameInfo.resultDetails = NULL;
18395         savedFirst[storedGames] = firstMove;
18396         savedLast [storedGames] = lastMove;
18397         savedFramePtr[storedGames] = framePtr;
18398         framePtr -= nrMoves; // reserve space for the boards
18399         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18400             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18401             for(j=0; j<MOVE_LEN; j++)
18402                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18403             for(j=0; j<2*MOVE_LEN; j++)
18404                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18405             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18406             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18407             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18408             pvInfoList[firstMove+i-1].depth = 0;
18409             commentList[framePtr+i] = commentList[firstMove+i];
18410             commentList[firstMove+i] = NULL;
18411         }
18412
18413         storedGames++;
18414         forwardMostMove = firstMove; // truncate game so we can start variation
18415 }
18416
18417 void
18418 PushTail (int firstMove, int lastMove)
18419 {
18420         if(appData.icsActive) { // only in local mode
18421                 forwardMostMove = currentMove; // mimic old ICS behavior
18422                 return;
18423         }
18424         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18425
18426         PushInner(firstMove, lastMove);
18427         if(storedGames == 1) GreyRevert(FALSE);
18428         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18429 }
18430
18431 void
18432 PopInner (Boolean annotate)
18433 {
18434         int i, j, nrMoves;
18435         char buf[8000], moveBuf[20];
18436
18437         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18438         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18439         nrMoves = savedLast[storedGames] - currentMove;
18440         if(annotate) {
18441                 int cnt = 10;
18442                 if(!WhiteOnMove(currentMove))
18443                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18444                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18445                 for(i=currentMove; i<forwardMostMove; i++) {
18446                         if(WhiteOnMove(i))
18447                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18448                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18449                         strcat(buf, moveBuf);
18450                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18451                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18452                 }
18453                 strcat(buf, ")");
18454         }
18455         for(i=1; i<=nrMoves; i++) { // copy last variation back
18456             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18457             for(j=0; j<MOVE_LEN; j++)
18458                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18459             for(j=0; j<2*MOVE_LEN; j++)
18460                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18461             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18462             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18463             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18464             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18465             commentList[currentMove+i] = commentList[framePtr+i];
18466             commentList[framePtr+i] = NULL;
18467         }
18468         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18469         framePtr = savedFramePtr[storedGames];
18470         gameInfo.result = savedResult[storedGames];
18471         if(gameInfo.resultDetails != NULL) {
18472             free(gameInfo.resultDetails);
18473       }
18474         gameInfo.resultDetails = savedDetails[storedGames];
18475         forwardMostMove = currentMove + nrMoves;
18476 }
18477
18478 Boolean
18479 PopTail (Boolean annotate)
18480 {
18481         if(appData.icsActive) return FALSE; // only in local mode
18482         if(!storedGames) return FALSE; // sanity
18483         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18484
18485         PopInner(annotate);
18486         if(currentMove < forwardMostMove) ForwardEvent(); else
18487         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18488
18489         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18490         return TRUE;
18491 }
18492
18493 void
18494 CleanupTail ()
18495 {       // remove all shelved variations
18496         int i;
18497         for(i=0; i<storedGames; i++) {
18498             if(savedDetails[i])
18499                 free(savedDetails[i]);
18500             savedDetails[i] = NULL;
18501         }
18502         for(i=framePtr; i<MAX_MOVES; i++) {
18503                 if(commentList[i]) free(commentList[i]);
18504                 commentList[i] = NULL;
18505         }
18506         framePtr = MAX_MOVES-1;
18507         storedGames = 0;
18508 }
18509
18510 void
18511 LoadVariation (int index, char *text)
18512 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18513         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18514         int level = 0, move;
18515
18516         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18517         // first find outermost bracketing variation
18518         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18519             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18520                 if(*p == '{') wait = '}'; else
18521                 if(*p == '[') wait = ']'; else
18522                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18523                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18524             }
18525             if(*p == wait) wait = NULLCHAR; // closing ]} found
18526             p++;
18527         }
18528         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18529         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18530         end[1] = NULLCHAR; // clip off comment beyond variation
18531         ToNrEvent(currentMove-1);
18532         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18533         // kludge: use ParsePV() to append variation to game
18534         move = currentMove;
18535         ParsePV(start, TRUE, TRUE);
18536         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18537         ClearPremoveHighlights();
18538         CommentPopDown();
18539         ToNrEvent(currentMove+1);
18540 }
18541
18542 void
18543 LoadTheme ()
18544 {
18545     char *p, *q, buf[MSG_SIZ];
18546     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18547         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18548         ParseArgsFromString(buf);
18549         ActivateTheme(TRUE); // also redo colors
18550         return;
18551     }
18552     p = nickName;
18553     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18554     {
18555         int len;
18556         q = appData.themeNames;
18557         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18558       if(appData.useBitmaps) {
18559         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18560                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18561                 appData.liteBackTextureMode,
18562                 appData.darkBackTextureMode );
18563       } else {
18564         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18565                 Col2Text(2),   // lightSquareColor
18566                 Col2Text(3) ); // darkSquareColor
18567       }
18568       if(appData.useBorder) {
18569         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18570                 appData.border);
18571       } else {
18572         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18573       }
18574       if(appData.useFont) {
18575         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18576                 appData.renderPiecesWithFont,
18577                 appData.fontToPieceTable,
18578                 Col2Text(9),    // appData.fontBackColorWhite
18579                 Col2Text(10) ); // appData.fontForeColorBlack
18580       } else {
18581         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18582                 appData.pieceDirectory);
18583         if(!appData.pieceDirectory[0])
18584           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18585                 Col2Text(0),   // whitePieceColor
18586                 Col2Text(1) ); // blackPieceColor
18587       }
18588       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18589                 Col2Text(4),   // highlightSquareColor
18590                 Col2Text(5) ); // premoveHighlightColor
18591         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18592         if(insert != q) insert[-1] = NULLCHAR;
18593         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18594         if(q)   free(q);
18595     }
18596     ActivateTheme(FALSE);
18597 }