Implement triple capture (not finished)
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144         char *m = moveList[moveNum];
5145         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1, kill2X, kill2Y; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) {
5352             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5353             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5354           }
5355         } else {
5356             sprintf(move, "%c%c%c%c%c\n",
5357                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5358         }
5359     }
5360 }
5361
5362 void
5363 ProcessICSInitScript (FILE *f)
5364 {
5365     char buf[MSG_SIZ];
5366
5367     while (fgets(buf, MSG_SIZ, f)) {
5368         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5369     }
5370
5371     fclose(f);
5372 }
5373
5374
5375 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5376 int dragging;
5377 static ClickType lastClickType;
5378
5379 int
5380 Partner (ChessSquare *p)
5381 { // change piece into promotion partner if one shogi-promotes to the other
5382   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5383   ChessSquare partner;
5384   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5385   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5386   *p = partner;
5387   return 1;
5388 }
5389
5390 void
5391 Sweep (int step)
5392 {
5393     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5394     static int toggleFlag;
5395     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5396     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5397     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5398     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5399     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5400     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5401     do {
5402         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5403         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5404         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5405         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5406         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5407         if(!step) step = -1;
5408     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5409             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5410             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5411             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5412     if(toX >= 0) {
5413         int victim = boards[currentMove][toY][toX];
5414         boards[currentMove][toY][toX] = promoSweep;
5415         DrawPosition(FALSE, boards[currentMove]);
5416         boards[currentMove][toY][toX] = victim;
5417     } else
5418     ChangeDragPiece(promoSweep);
5419 }
5420
5421 int
5422 PromoScroll (int x, int y)
5423 {
5424   int step = 0;
5425
5426   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5427   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5428   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5429   if(!step) return FALSE;
5430   lastX = x; lastY = y;
5431   if((promoSweep < BlackPawn) == flipView) step = -step;
5432   if(step > 0) selectFlag = 1;
5433   if(!selectFlag) Sweep(step);
5434   return FALSE;
5435 }
5436
5437 void
5438 NextPiece (int step)
5439 {
5440     ChessSquare piece = boards[currentMove][toY][toX];
5441     do {
5442         pieceSweep -= step;
5443         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5444         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5445         if(!step) step = -1;
5446     } while(PieceToChar(pieceSweep) == '.');
5447     boards[currentMove][toY][toX] = pieceSweep;
5448     DrawPosition(FALSE, boards[currentMove]);
5449     boards[currentMove][toY][toX] = piece;
5450 }
5451 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5452 void
5453 AlphaRank (char *move, int n)
5454 {
5455 //    char *p = move, c; int x, y;
5456
5457     if (appData.debugMode) {
5458         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5459     }
5460
5461     if(move[1]=='*' &&
5462        move[2]>='0' && move[2]<='9' &&
5463        move[3]>='a' && move[3]<='x'    ) {
5464         move[1] = '@';
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[0]>='0' && move[0]<='9' &&
5469        move[1]>='a' && move[1]<='x' &&
5470        move[2]>='0' && move[2]<='9' &&
5471        move[3]>='a' && move[3]<='x'    ) {
5472         /* input move, Shogi -> normal */
5473         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5474         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5475         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5476         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5477     } else
5478     if(move[1]=='@' &&
5479        move[3]>='0' && move[3]<='9' &&
5480        move[2]>='a' && move[2]<='x'    ) {
5481         move[1] = '*';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484     } else
5485     if(
5486        move[0]>='a' && move[0]<='x' &&
5487        move[3]>='0' && move[3]<='9' &&
5488        move[2]>='a' && move[2]<='x'    ) {
5489          /* output move, normal -> Shogi */
5490         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5491         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5492         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5493         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5494         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5495     }
5496     if (appData.debugMode) {
5497         fprintf(debugFP, "   out = '%s'\n", move);
5498     }
5499 }
5500
5501 char yy_textstr[8000];
5502
5503 /* Parser for moves from gnuchess, ICS, or user typein box */
5504 Boolean
5505 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5506 {
5507     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5508
5509     switch (*moveType) {
5510       case WhitePromotion:
5511       case BlackPromotion:
5512       case WhiteNonPromotion:
5513       case BlackNonPromotion:
5514       case NormalMove:
5515       case FirstLeg:
5516       case WhiteCapturesEnPassant:
5517       case BlackCapturesEnPassant:
5518       case WhiteKingSideCastle:
5519       case WhiteQueenSideCastle:
5520       case BlackKingSideCastle:
5521       case BlackQueenSideCastle:
5522       case WhiteKingSideCastleWild:
5523       case WhiteQueenSideCastleWild:
5524       case BlackKingSideCastleWild:
5525       case BlackQueenSideCastleWild:
5526       /* Code added by Tord: */
5527       case WhiteHSideCastleFR:
5528       case WhiteASideCastleFR:
5529       case BlackHSideCastleFR:
5530       case BlackASideCastleFR:
5531       /* End of code added by Tord */
5532       case IllegalMove:         /* bug or odd chess variant */
5533         if(currentMoveString[1] == '@') { // illegal drop
5534           *fromX = WhiteOnMove(moveNum) ?
5535             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536             (int) CharToPiece(ToLower(currentMoveString[0]));
5537           goto drop;
5538         }
5539         *fromX = currentMoveString[0] - AAA;
5540         *fromY = currentMoveString[1] - ONE;
5541         *toX = currentMoveString[2] - AAA;
5542         *toY = currentMoveString[3] - ONE;
5543         *promoChar = currentMoveString[4];
5544         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5545             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5546     if (appData.debugMode) {
5547         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5548     }
5549             *fromX = *fromY = *toX = *toY = 0;
5550             return FALSE;
5551         }
5552         if (appData.testLegality) {
5553           return (*moveType != IllegalMove);
5554         } else {
5555           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5556                          // [HGM] lion: if this is a double move we are less critical
5557                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5558         }
5559
5560       case WhiteDrop:
5561       case BlackDrop:
5562         *fromX = *moveType == WhiteDrop ?
5563           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5564           (int) CharToPiece(ToLower(currentMoveString[0]));
5565       drop:
5566         *fromY = DROP_RANK;
5567         *toX = currentMoveString[2] - AAA;
5568         *toY = currentMoveString[3] - ONE;
5569         *promoChar = NULLCHAR;
5570         return TRUE;
5571
5572       case AmbiguousMove:
5573       case ImpossibleMove:
5574       case EndOfFile:
5575       case ElapsedTime:
5576       case Comment:
5577       case PGNTag:
5578       case NAG:
5579       case WhiteWins:
5580       case BlackWins:
5581       case GameIsDrawn:
5582       default:
5583     if (appData.debugMode) {
5584         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5585     }
5586         /* bug? */
5587         *fromX = *fromY = *toX = *toY = 0;
5588         *promoChar = NULLCHAR;
5589         return FALSE;
5590     }
5591 }
5592
5593 Boolean pushed = FALSE;
5594 char *lastParseAttempt;
5595
5596 void
5597 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5598 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5599   int fromX, fromY, toX, toY; char promoChar;
5600   ChessMove moveType;
5601   Boolean valid;
5602   int nr = 0;
5603
5604   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5605   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5606     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5607     pushed = TRUE;
5608   }
5609   endPV = forwardMostMove;
5610   do {
5611     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5612     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5613     lastParseAttempt = pv;
5614     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5615     if(!valid && nr == 0 &&
5616        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5617         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5618         // Hande case where played move is different from leading PV move
5619         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5620         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5621         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5622         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5623           endPV += 2; // if position different, keep this
5624           moveList[endPV-1][0] = fromX + AAA;
5625           moveList[endPV-1][1] = fromY + ONE;
5626           moveList[endPV-1][2] = toX + AAA;
5627           moveList[endPV-1][3] = toY + ONE;
5628           parseList[endPV-1][0] = NULLCHAR;
5629           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5630         }
5631       }
5632     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5633     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5634     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5635     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5636         valid++; // allow comments in PV
5637         continue;
5638     }
5639     nr++;
5640     if(endPV+1 > framePtr) break; // no space, truncate
5641     if(!valid) break;
5642     endPV++;
5643     CopyBoard(boards[endPV], boards[endPV-1]);
5644     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5645     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5646     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5647     CoordsToAlgebraic(boards[endPV - 1],
5648                              PosFlags(endPV - 1),
5649                              fromY, fromX, toY, toX, promoChar,
5650                              parseList[endPV - 1]);
5651   } while(valid);
5652   if(atEnd == 2) return; // used hidden, for PV conversion
5653   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5654   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5655   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5656                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5657   DrawPosition(TRUE, boards[currentMove]);
5658 }
5659
5660 int
5661 MultiPV (ChessProgramState *cps)
5662 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5663         int i;
5664         for(i=0; i<cps->nrOptions; i++)
5665             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5666                 return i;
5667         return -1;
5668 }
5669
5670 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5671
5672 Boolean
5673 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5674 {
5675         int startPV, multi, lineStart, origIndex = index;
5676         char *p, buf2[MSG_SIZ];
5677         ChessProgramState *cps = (pane ? &second : &first);
5678
5679         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5680         lastX = x; lastY = y;
5681         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5682         lineStart = startPV = index;
5683         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5684         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5685         index = startPV;
5686         do{ while(buf[index] && buf[index] != '\n') index++;
5687         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5688         buf[index] = 0;
5689         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5690                 int n = cps->option[multi].value;
5691                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5692                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5693                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5694                 cps->option[multi].value = n;
5695                 *start = *end = 0;
5696                 return FALSE;
5697         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5698                 ExcludeClick(origIndex - lineStart);
5699                 return FALSE;
5700         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5701                 Collapse(origIndex - lineStart);
5702                 return FALSE;
5703         }
5704         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5705         *start = startPV; *end = index-1;
5706         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5707         return TRUE;
5708 }
5709
5710 char *
5711 PvToSAN (char *pv)
5712 {
5713         static char buf[10*MSG_SIZ];
5714         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5715         *buf = NULLCHAR;
5716         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5717         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5718         for(i = forwardMostMove; i<endPV; i++){
5719             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5720             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5721             k += strlen(buf+k);
5722         }
5723         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5724         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5725         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5726         endPV = savedEnd;
5727         return buf;
5728 }
5729
5730 Boolean
5731 LoadPV (int x, int y)
5732 { // called on right mouse click to load PV
5733   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5734   lastX = x; lastY = y;
5735   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5736   extendGame = FALSE;
5737   return TRUE;
5738 }
5739
5740 void
5741 UnLoadPV ()
5742 {
5743   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5744   if(endPV < 0) return;
5745   if(appData.autoCopyPV) CopyFENToClipboard();
5746   endPV = -1;
5747   if(extendGame && currentMove > forwardMostMove) {
5748         Boolean saveAnimate = appData.animate;
5749         if(pushed) {
5750             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5751                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5752             } else storedGames--; // abandon shelved tail of original game
5753         }
5754         pushed = FALSE;
5755         forwardMostMove = currentMove;
5756         currentMove = oldFMM;
5757         appData.animate = FALSE;
5758         ToNrEvent(forwardMostMove);
5759         appData.animate = saveAnimate;
5760   }
5761   currentMove = forwardMostMove;
5762   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5763   ClearPremoveHighlights();
5764   DrawPosition(TRUE, boards[currentMove]);
5765 }
5766
5767 void
5768 MovePV (int x, int y, int h)
5769 { // step through PV based on mouse coordinates (called on mouse move)
5770   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5771
5772   // we must somehow check if right button is still down (might be released off board!)
5773   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5774   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5775   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5776   if(!step) return;
5777   lastX = x; lastY = y;
5778
5779   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5780   if(endPV < 0) return;
5781   if(y < margin) step = 1; else
5782   if(y > h - margin) step = -1;
5783   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5784   currentMove += step;
5785   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5786   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5787                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5788   DrawPosition(FALSE, boards[currentMove]);
5789 }
5790
5791
5792 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5793 // All positions will have equal probability, but the current method will not provide a unique
5794 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5795 #define DARK 1
5796 #define LITE 2
5797 #define ANY 3
5798
5799 int squaresLeft[4];
5800 int piecesLeft[(int)BlackPawn];
5801 int seed, nrOfShuffles;
5802
5803 void
5804 GetPositionNumber ()
5805 {       // sets global variable seed
5806         int i;
5807
5808         seed = appData.defaultFrcPosition;
5809         if(seed < 0) { // randomize based on time for negative FRC position numbers
5810                 for(i=0; i<50; i++) seed += random();
5811                 seed = random() ^ random() >> 8 ^ random() << 8;
5812                 if(seed<0) seed = -seed;
5813         }
5814 }
5815
5816 int
5817 put (Board board, int pieceType, int rank, int n, int shade)
5818 // put the piece on the (n-1)-th empty squares of the given shade
5819 {
5820         int i;
5821
5822         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5823                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5824                         board[rank][i] = (ChessSquare) pieceType;
5825                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5826                         squaresLeft[ANY]--;
5827                         piecesLeft[pieceType]--;
5828                         return i;
5829                 }
5830         }
5831         return -1;
5832 }
5833
5834
5835 void
5836 AddOnePiece (Board board, int pieceType, int rank, int shade)
5837 // calculate where the next piece goes, (any empty square), and put it there
5838 {
5839         int i;
5840
5841         i = seed % squaresLeft[shade];
5842         nrOfShuffles *= squaresLeft[shade];
5843         seed /= squaresLeft[shade];
5844         put(board, pieceType, rank, i, shade);
5845 }
5846
5847 void
5848 AddTwoPieces (Board board, int pieceType, int rank)
5849 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5850 {
5851         int i, n=squaresLeft[ANY], j=n-1, k;
5852
5853         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5854         i = seed % k;  // pick one
5855         nrOfShuffles *= k;
5856         seed /= k;
5857         while(i >= j) i -= j--;
5858         j = n - 1 - j; i += j;
5859         put(board, pieceType, rank, j, ANY);
5860         put(board, pieceType, rank, i, ANY);
5861 }
5862
5863 void
5864 SetUpShuffle (Board board, int number)
5865 {
5866         int i, p, first=1;
5867
5868         GetPositionNumber(); nrOfShuffles = 1;
5869
5870         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5871         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5872         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5873
5874         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5875
5876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5877             p = (int) board[0][i];
5878             if(p < (int) BlackPawn) piecesLeft[p] ++;
5879             board[0][i] = EmptySquare;
5880         }
5881
5882         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5883             // shuffles restricted to allow normal castling put KRR first
5884             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5885                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5886             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5887                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5888             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5889                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5890             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5891                 put(board, WhiteRook, 0, 0, ANY);
5892             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5893         }
5894
5895         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5896             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5897             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5898                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5899                 while(piecesLeft[p] >= 2) {
5900                     AddOnePiece(board, p, 0, LITE);
5901                     AddOnePiece(board, p, 0, DARK);
5902                 }
5903                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5904             }
5905
5906         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5907             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5908             // but we leave King and Rooks for last, to possibly obey FRC restriction
5909             if(p == (int)WhiteRook) continue;
5910             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5911             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5912         }
5913
5914         // now everything is placed, except perhaps King (Unicorn) and Rooks
5915
5916         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5917             // Last King gets castling rights
5918             while(piecesLeft[(int)WhiteUnicorn]) {
5919                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5920                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5921             }
5922
5923             while(piecesLeft[(int)WhiteKing]) {
5924                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5925                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5926             }
5927
5928
5929         } else {
5930             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5931             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5932         }
5933
5934         // Only Rooks can be left; simply place them all
5935         while(piecesLeft[(int)WhiteRook]) {
5936                 i = put(board, WhiteRook, 0, 0, ANY);
5937                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5938                         if(first) {
5939                                 first=0;
5940                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5941                         }
5942                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5943                 }
5944         }
5945         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5946             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5947         }
5948
5949         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5950 }
5951
5952 int
5953 ptclen (const char *s, char *escapes)
5954 {
5955     int n = 0;
5956     if(!*escapes) return strlen(s);
5957     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5958     return n;
5959 }
5960
5961 int
5962 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5963 /* [HGM] moved here from winboard.c because of its general usefulness */
5964 /*       Basically a safe strcpy that uses the last character as King */
5965 {
5966     int result = FALSE; int NrPieces;
5967
5968     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5969                     && NrPieces >= 12 && !(NrPieces&1)) {
5970         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5971
5972         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5973         for( i=0; i<NrPieces/2-1; i++ ) {
5974             char *p;
5975             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5976             table[i] = map[j++];
5977             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5978         }
5979         table[(int) WhiteKing]  = map[j++];
5980         for( i=0; i<NrPieces/2-1; i++ ) {
5981             char *p;
5982             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5983             table[WHITE_TO_BLACK i] = map[j++];
5984             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5985         }
5986         table[(int) BlackKing]  = map[j++];
5987
5988         result = TRUE;
5989     }
5990
5991     return result;
5992 }
5993
5994 int
5995 SetCharTable (unsigned char *table, const char * map)
5996 {
5997     return SetCharTableEsc(table, map, "");
5998 }
5999
6000 void
6001 Prelude (Board board)
6002 {       // [HGM] superchess: random selection of exo-pieces
6003         int i, j, k; ChessSquare p;
6004         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6005
6006         GetPositionNumber(); // use FRC position number
6007
6008         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6009             SetCharTable(pieceToChar, appData.pieceToCharTable);
6010             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6011                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6012         }
6013
6014         j = seed%4;                 seed /= 4;
6015         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6016         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6017         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6018         j = seed%3 + (seed%3 >= j); seed /= 3;
6019         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6020         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6021         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6022         j = seed%3;                 seed /= 3;
6023         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6024         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6025         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6026         j = seed%2 + (seed%2 >= j); seed /= 2;
6027         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6028         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6029         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6030         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6031         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6032         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6033         put(board, exoPieces[0],    0, 0, ANY);
6034         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6035 }
6036
6037 void
6038 InitPosition (int redraw)
6039 {
6040     ChessSquare (* pieces)[BOARD_FILES];
6041     int i, j, pawnRow=1, pieceRows=1, overrule,
6042     oldx = gameInfo.boardWidth,
6043     oldy = gameInfo.boardHeight,
6044     oldh = gameInfo.holdingsWidth;
6045     static int oldv;
6046
6047     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6048
6049     /* [AS] Initialize pv info list [HGM] and game status */
6050     {
6051         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6052             pvInfoList[i].depth = 0;
6053             boards[i][EP_STATUS] = EP_NONE;
6054             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6055         }
6056
6057         initialRulePlies = 0; /* 50-move counter start */
6058
6059         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6060         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6061     }
6062
6063
6064     /* [HGM] logic here is completely changed. In stead of full positions */
6065     /* the initialized data only consist of the two backranks. The switch */
6066     /* selects which one we will use, which is than copied to the Board   */
6067     /* initialPosition, which for the rest is initialized by Pawns and    */
6068     /* empty squares. This initial position is then copied to boards[0],  */
6069     /* possibly after shuffling, so that it remains available.            */
6070
6071     gameInfo.holdingsWidth = 0; /* default board sizes */
6072     gameInfo.boardWidth    = 8;
6073     gameInfo.boardHeight   = 8;
6074     gameInfo.holdingsSize  = 0;
6075     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6076     for(i=0; i<BOARD_FILES-6; i++)
6077       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6078     initialPosition[EP_STATUS] = EP_NONE;
6079     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6080     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6081     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6082          SetCharTable(pieceNickName, appData.pieceNickNames);
6083     else SetCharTable(pieceNickName, "............");
6084     pieces = FIDEArray;
6085
6086     switch (gameInfo.variant) {
6087     case VariantFischeRandom:
6088       shuffleOpenings = TRUE;
6089       appData.fischerCastling = TRUE;
6090     default:
6091       break;
6092     case VariantShatranj:
6093       pieces = ShatranjArray;
6094       nrCastlingRights = 0;
6095       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6096       break;
6097     case VariantMakruk:
6098       pieces = makrukArray;
6099       nrCastlingRights = 0;
6100       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6101       break;
6102     case VariantASEAN:
6103       pieces = aseanArray;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6106       break;
6107     case VariantTwoKings:
6108       pieces = twoKingsArray;
6109       break;
6110     case VariantGrand:
6111       pieces = GrandArray;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6114       gameInfo.boardWidth = 10;
6115       gameInfo.boardHeight = 10;
6116       gameInfo.holdingsSize = 7;
6117       break;
6118     case VariantCapaRandom:
6119       shuffleOpenings = TRUE;
6120       appData.fischerCastling = TRUE;
6121     case VariantCapablanca:
6122       pieces = CapablancaArray;
6123       gameInfo.boardWidth = 10;
6124       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6125       break;
6126     case VariantGothic:
6127       pieces = GothicArray;
6128       gameInfo.boardWidth = 10;
6129       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6130       break;
6131     case VariantSChess:
6132       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6133       gameInfo.holdingsSize = 7;
6134       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6135       break;
6136     case VariantJanus:
6137       pieces = JanusArray;
6138       gameInfo.boardWidth = 10;
6139       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6140       nrCastlingRights = 6;
6141         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6142         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6143         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6144         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6145         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6146         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6147       break;
6148     case VariantFalcon:
6149       pieces = FalconArray;
6150       gameInfo.boardWidth = 10;
6151       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6152       break;
6153     case VariantXiangqi:
6154       pieces = XiangqiArray;
6155       gameInfo.boardWidth  = 9;
6156       gameInfo.boardHeight = 10;
6157       nrCastlingRights = 0;
6158       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6159       break;
6160     case VariantShogi:
6161       pieces = ShogiArray;
6162       gameInfo.boardWidth  = 9;
6163       gameInfo.boardHeight = 9;
6164       gameInfo.holdingsSize = 7;
6165       nrCastlingRights = 0;
6166       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6167       break;
6168     case VariantChu:
6169       pieces = ChuArray; pieceRows = 3;
6170       gameInfo.boardWidth  = 12;
6171       gameInfo.boardHeight = 12;
6172       nrCastlingRights = 0;
6173       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6174                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6175       break;
6176     case VariantCourier:
6177       pieces = CourierArray;
6178       gameInfo.boardWidth  = 12;
6179       nrCastlingRights = 0;
6180       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6181       break;
6182     case VariantKnightmate:
6183       pieces = KnightmateArray;
6184       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6185       break;
6186     case VariantSpartan:
6187       pieces = SpartanArray;
6188       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6189       break;
6190     case VariantLion:
6191       pieces = lionArray;
6192       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6193       break;
6194     case VariantChuChess:
6195       pieces = ChuChessArray;
6196       gameInfo.boardWidth = 10;
6197       gameInfo.boardHeight = 10;
6198       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6199       break;
6200     case VariantFairy:
6201       pieces = fairyArray;
6202       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6203       break;
6204     case VariantGreat:
6205       pieces = GreatArray;
6206       gameInfo.boardWidth = 10;
6207       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6208       gameInfo.holdingsSize = 8;
6209       break;
6210     case VariantSuper:
6211       pieces = FIDEArray;
6212       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6213       gameInfo.holdingsSize = 8;
6214       startedFromSetupPosition = TRUE;
6215       break;
6216     case VariantCrazyhouse:
6217     case VariantBughouse:
6218       pieces = FIDEArray;
6219       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6220       gameInfo.holdingsSize = 5;
6221       break;
6222     case VariantWildCastle:
6223       pieces = FIDEArray;
6224       /* !!?shuffle with kings guaranteed to be on d or e file */
6225       shuffleOpenings = 1;
6226       break;
6227     case VariantNoCastle:
6228       pieces = FIDEArray;
6229       nrCastlingRights = 0;
6230       /* !!?unconstrained back-rank shuffle */
6231       shuffleOpenings = 1;
6232       break;
6233     }
6234
6235     overrule = 0;
6236     if(appData.NrFiles >= 0) {
6237         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6238         gameInfo.boardWidth = appData.NrFiles;
6239     }
6240     if(appData.NrRanks >= 0) {
6241         gameInfo.boardHeight = appData.NrRanks;
6242     }
6243     if(appData.holdingsSize >= 0) {
6244         i = appData.holdingsSize;
6245         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6246         gameInfo.holdingsSize = i;
6247     }
6248     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6249     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6250         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6251
6252     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6253     if(pawnRow < 1) pawnRow = 1;
6254     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6255        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6256     if(gameInfo.variant == VariantChu) pawnRow = 3;
6257
6258     /* User pieceToChar list overrules defaults */
6259     if(appData.pieceToCharTable != NULL)
6260         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6261
6262     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6263
6264         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6265             s = (ChessSquare) 0; /* account holding counts in guard band */
6266         for( i=0; i<BOARD_HEIGHT; i++ )
6267             initialPosition[i][j] = s;
6268
6269         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6270         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6271         initialPosition[pawnRow][j] = WhitePawn;
6272         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6273         if(gameInfo.variant == VariantXiangqi) {
6274             if(j&1) {
6275                 initialPosition[pawnRow][j] =
6276                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6277                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6278                    initialPosition[2][j] = WhiteCannon;
6279                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6280                 }
6281             }
6282         }
6283         if(gameInfo.variant == VariantChu) {
6284              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6285                initialPosition[pawnRow+1][j] = WhiteCobra,
6286                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6287              for(i=1; i<pieceRows; i++) {
6288                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6289                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6290              }
6291         }
6292         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6293             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6294                initialPosition[0][j] = WhiteRook;
6295                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6296             }
6297         }
6298         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6299     }
6300     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6301     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6302
6303             j=BOARD_LEFT+1;
6304             initialPosition[1][j] = WhiteBishop;
6305             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6306             j=BOARD_RGHT-2;
6307             initialPosition[1][j] = WhiteRook;
6308             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6309     }
6310
6311     if( nrCastlingRights == -1) {
6312         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6313         /*       This sets default castling rights from none to normal corners   */
6314         /* Variants with other castling rights must set them themselves above    */
6315         nrCastlingRights = 6;
6316
6317         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6318         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6319         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6320         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6321         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6322         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6323      }
6324
6325      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6326      if(gameInfo.variant == VariantGreat) { // promotion commoners
6327         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6328         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6329         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6330         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6331      }
6332      if( gameInfo.variant == VariantSChess ) {
6333       initialPosition[1][0] = BlackMarshall;
6334       initialPosition[2][0] = BlackAngel;
6335       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6336       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6337       initialPosition[1][1] = initialPosition[2][1] =
6338       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6339      }
6340   if (appData.debugMode) {
6341     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6342   }
6343     if(shuffleOpenings) {
6344         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6345         startedFromSetupPosition = TRUE;
6346     }
6347     if(startedFromPositionFile) {
6348       /* [HGM] loadPos: use PositionFile for every new game */
6349       CopyBoard(initialPosition, filePosition);
6350       for(i=0; i<nrCastlingRights; i++)
6351           initialRights[i] = filePosition[CASTLING][i];
6352       startedFromSetupPosition = TRUE;
6353     }
6354
6355     CopyBoard(boards[0], initialPosition);
6356
6357     if(oldx != gameInfo.boardWidth ||
6358        oldy != gameInfo.boardHeight ||
6359        oldv != gameInfo.variant ||
6360        oldh != gameInfo.holdingsWidth
6361                                          )
6362             InitDrawingSizes(-2 ,0);
6363
6364     oldv = gameInfo.variant;
6365     if (redraw)
6366       DrawPosition(TRUE, boards[currentMove]);
6367 }
6368
6369 void
6370 SendBoard (ChessProgramState *cps, int moveNum)
6371 {
6372     char message[MSG_SIZ];
6373
6374     if (cps->useSetboard) {
6375       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6376       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6377       SendToProgram(message, cps);
6378       free(fen);
6379
6380     } else {
6381       ChessSquare *bp;
6382       int i, j, left=0, right=BOARD_WIDTH;
6383       /* Kludge to set black to move, avoiding the troublesome and now
6384        * deprecated "black" command.
6385        */
6386       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6387         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6388
6389       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6390
6391       SendToProgram("edit\n", cps);
6392       SendToProgram("#\n", cps);
6393       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6394         bp = &boards[moveNum][i][left];
6395         for (j = left; j < right; j++, bp++) {
6396           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6397           if ((int) *bp < (int) BlackPawn) {
6398             if(j == BOARD_RGHT+1)
6399                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6400             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6401             if(message[0] == '+' || message[0] == '~') {
6402               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6403                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6404                         AAA + j, ONE + i);
6405             }
6406             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6407                 message[1] = BOARD_RGHT   - 1 - j + '1';
6408                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6409             }
6410             SendToProgram(message, cps);
6411           }
6412         }
6413       }
6414
6415       SendToProgram("c\n", cps);
6416       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6417         bp = &boards[moveNum][i][left];
6418         for (j = left; j < right; j++, bp++) {
6419           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6420           if (((int) *bp != (int) EmptySquare)
6421               && ((int) *bp >= (int) BlackPawn)) {
6422             if(j == BOARD_LEFT-2)
6423                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6424             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6425                     AAA + j, ONE + i);
6426             if(message[0] == '+' || message[0] == '~') {
6427               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6428                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6429                         AAA + j, ONE + i);
6430             }
6431             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6432                 message[1] = BOARD_RGHT   - 1 - j + '1';
6433                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6434             }
6435             SendToProgram(message, cps);
6436           }
6437         }
6438       }
6439
6440       SendToProgram(".\n", cps);
6441     }
6442     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6443 }
6444
6445 char exclusionHeader[MSG_SIZ];
6446 int exCnt, excludePtr;
6447 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6448 static Exclusion excluTab[200];
6449 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6450
6451 static void
6452 WriteMap (int s)
6453 {
6454     int j;
6455     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6456     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6457 }
6458
6459 static void
6460 ClearMap ()
6461 {
6462     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6463     excludePtr = 24; exCnt = 0;
6464     WriteMap(0);
6465 }
6466
6467 static void
6468 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6469 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6470     char buf[2*MOVE_LEN], *p;
6471     Exclusion *e = excluTab;
6472     int i;
6473     for(i=0; i<exCnt; i++)
6474         if(e[i].ff == fromX && e[i].fr == fromY &&
6475            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6476     if(i == exCnt) { // was not in exclude list; add it
6477         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6478         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6479             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6480             return; // abort
6481         }
6482         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6483         excludePtr++; e[i].mark = excludePtr++;
6484         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6485         exCnt++;
6486     }
6487     exclusionHeader[e[i].mark] = state;
6488 }
6489
6490 static int
6491 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6492 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6493     char buf[MSG_SIZ];
6494     int j, k;
6495     ChessMove moveType;
6496     if((signed char)promoChar == -1) { // kludge to indicate best move
6497         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6498             return 1; // if unparsable, abort
6499     }
6500     // update exclusion map (resolving toggle by consulting existing state)
6501     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6502     j = k%8; k >>= 3;
6503     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6504     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6505          excludeMap[k] |=   1<<j;
6506     else excludeMap[k] &= ~(1<<j);
6507     // update header
6508     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6509     // inform engine
6510     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6511     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6512     SendToBoth(buf);
6513     return (state == '+');
6514 }
6515
6516 static void
6517 ExcludeClick (int index)
6518 {
6519     int i, j;
6520     Exclusion *e = excluTab;
6521     if(index < 25) { // none, best or tail clicked
6522         if(index < 13) { // none: include all
6523             WriteMap(0); // clear map
6524             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6525             SendToBoth("include all\n"); // and inform engine
6526         } else if(index > 18) { // tail
6527             if(exclusionHeader[19] == '-') { // tail was excluded
6528                 SendToBoth("include all\n");
6529                 WriteMap(0); // clear map completely
6530                 // now re-exclude selected moves
6531                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6532                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6533             } else { // tail was included or in mixed state
6534                 SendToBoth("exclude all\n");
6535                 WriteMap(0xFF); // fill map completely
6536                 // now re-include selected moves
6537                 j = 0; // count them
6538                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6539                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6540                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6541             }
6542         } else { // best
6543             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6544         }
6545     } else {
6546         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6547             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6548             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6549             break;
6550         }
6551     }
6552 }
6553
6554 ChessSquare
6555 DefaultPromoChoice (int white)
6556 {
6557     ChessSquare result;
6558     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6559        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6560         result = WhiteFerz; // no choice
6561     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6562         result= WhiteKing; // in Suicide Q is the last thing we want
6563     else if(gameInfo.variant == VariantSpartan)
6564         result = white ? WhiteQueen : WhiteAngel;
6565     else result = WhiteQueen;
6566     if(!white) result = WHITE_TO_BLACK result;
6567     return result;
6568 }
6569
6570 static int autoQueen; // [HGM] oneclick
6571
6572 int
6573 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6574 {
6575     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6576     /* [HGM] add Shogi promotions */
6577     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6578     ChessSquare piece, partner;
6579     ChessMove moveType;
6580     Boolean premove;
6581
6582     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6583     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6584
6585     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6586       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6587         return FALSE;
6588
6589     piece = boards[currentMove][fromY][fromX];
6590     if(gameInfo.variant == VariantChu) {
6591         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6592         promotionZoneSize = BOARD_HEIGHT/3;
6593         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6594     } else if(gameInfo.variant == VariantShogi) {
6595         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6596         highestPromotingPiece = (int)WhiteAlfil;
6597     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6598         promotionZoneSize = 3;
6599     }
6600
6601     // Treat Lance as Pawn when it is not representing Amazon or Lance
6602     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6603         if(piece == WhiteLance) piece = WhitePawn; else
6604         if(piece == BlackLance) piece = BlackPawn;
6605     }
6606
6607     // next weed out all moves that do not touch the promotion zone at all
6608     if((int)piece >= BlackPawn) {
6609         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6610              return FALSE;
6611         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6612         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6613     } else {
6614         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6615            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6616         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6617              return FALSE;
6618     }
6619
6620     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6621
6622     // weed out mandatory Shogi promotions
6623     if(gameInfo.variant == VariantShogi) {
6624         if(piece >= BlackPawn) {
6625             if(toY == 0 && piece == BlackPawn ||
6626                toY == 0 && piece == BlackQueen ||
6627                toY <= 1 && piece == BlackKnight) {
6628                 *promoChoice = '+';
6629                 return FALSE;
6630             }
6631         } else {
6632             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6633                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6634                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6635                 *promoChoice = '+';
6636                 return FALSE;
6637             }
6638         }
6639     }
6640
6641     // weed out obviously illegal Pawn moves
6642     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6643         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6644         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6645         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6646         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6647         // note we are not allowed to test for valid (non-)capture, due to premove
6648     }
6649
6650     // we either have a choice what to promote to, or (in Shogi) whether to promote
6651     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6652        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6653         ChessSquare p=BlackFerz;  // no choice
6654         while(p < EmptySquare) {  //but make sure we use piece that exists
6655             *promoChoice = PieceToChar(p++);
6656             if(*promoChoice != '.') break;
6657         }
6658         return FALSE;
6659     }
6660     // no sense asking what we must promote to if it is going to explode...
6661     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6662         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6663         return FALSE;
6664     }
6665     // give caller the default choice even if we will not make it
6666     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6667     partner = piece; // pieces can promote if the pieceToCharTable says so
6668     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6669     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6670     if(        sweepSelect && gameInfo.variant != VariantGreat
6671                            && gameInfo.variant != VariantGrand
6672                            && gameInfo.variant != VariantSuper) return FALSE;
6673     if(autoQueen) return FALSE; // predetermined
6674
6675     // suppress promotion popup on illegal moves that are not premoves
6676     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6677               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6678     if(appData.testLegality && !premove) {
6679         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6680                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6681         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6682         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6683             return FALSE;
6684     }
6685
6686     return TRUE;
6687 }
6688
6689 int
6690 InPalace (int row, int column)
6691 {   /* [HGM] for Xiangqi */
6692     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6693          column < (BOARD_WIDTH + 4)/2 &&
6694          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6695     return FALSE;
6696 }
6697
6698 int
6699 PieceForSquare (int x, int y)
6700 {
6701   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6702      return -1;
6703   else
6704      return boards[currentMove][y][x];
6705 }
6706
6707 int
6708 OKToStartUserMove (int x, int y)
6709 {
6710     ChessSquare from_piece;
6711     int white_piece;
6712
6713     if (matchMode) return FALSE;
6714     if (gameMode == EditPosition) return TRUE;
6715
6716     if (x >= 0 && y >= 0)
6717       from_piece = boards[currentMove][y][x];
6718     else
6719       from_piece = EmptySquare;
6720
6721     if (from_piece == EmptySquare) return FALSE;
6722
6723     white_piece = (int)from_piece >= (int)WhitePawn &&
6724       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6725
6726     switch (gameMode) {
6727       case AnalyzeFile:
6728       case TwoMachinesPlay:
6729       case EndOfGame:
6730         return FALSE;
6731
6732       case IcsObserving:
6733       case IcsIdle:
6734         return FALSE;
6735
6736       case MachinePlaysWhite:
6737       case IcsPlayingBlack:
6738         if (appData.zippyPlay) return FALSE;
6739         if (white_piece) {
6740             DisplayMoveError(_("You are playing Black"));
6741             return FALSE;
6742         }
6743         break;
6744
6745       case MachinePlaysBlack:
6746       case IcsPlayingWhite:
6747         if (appData.zippyPlay) return FALSE;
6748         if (!white_piece) {
6749             DisplayMoveError(_("You are playing White"));
6750             return FALSE;
6751         }
6752         break;
6753
6754       case PlayFromGameFile:
6755             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6756       case EditGame:
6757         if (!white_piece && WhiteOnMove(currentMove)) {
6758             DisplayMoveError(_("It is White's turn"));
6759             return FALSE;
6760         }
6761         if (white_piece && !WhiteOnMove(currentMove)) {
6762             DisplayMoveError(_("It is Black's turn"));
6763             return FALSE;
6764         }
6765         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6766             /* Editing correspondence game history */
6767             /* Could disallow this or prompt for confirmation */
6768             cmailOldMove = -1;
6769         }
6770         break;
6771
6772       case BeginningOfGame:
6773         if (appData.icsActive) return FALSE;
6774         if (!appData.noChessProgram) {
6775             if (!white_piece) {
6776                 DisplayMoveError(_("You are playing White"));
6777                 return FALSE;
6778             }
6779         }
6780         break;
6781
6782       case Training:
6783         if (!white_piece && WhiteOnMove(currentMove)) {
6784             DisplayMoveError(_("It is White's turn"));
6785             return FALSE;
6786         }
6787         if (white_piece && !WhiteOnMove(currentMove)) {
6788             DisplayMoveError(_("It is Black's turn"));
6789             return FALSE;
6790         }
6791         break;
6792
6793       default:
6794       case IcsExamining:
6795         break;
6796     }
6797     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6798         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6799         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6800         && gameMode != AnalyzeFile && gameMode != Training) {
6801         DisplayMoveError(_("Displayed position is not current"));
6802         return FALSE;
6803     }
6804     return TRUE;
6805 }
6806
6807 Boolean
6808 OnlyMove (int *x, int *y, Boolean captures)
6809 {
6810     DisambiguateClosure cl;
6811     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6812     switch(gameMode) {
6813       case MachinePlaysBlack:
6814       case IcsPlayingWhite:
6815       case BeginningOfGame:
6816         if(!WhiteOnMove(currentMove)) return FALSE;
6817         break;
6818       case MachinePlaysWhite:
6819       case IcsPlayingBlack:
6820         if(WhiteOnMove(currentMove)) return FALSE;
6821         break;
6822       case EditGame:
6823         break;
6824       default:
6825         return FALSE;
6826     }
6827     cl.pieceIn = EmptySquare;
6828     cl.rfIn = *y;
6829     cl.ffIn = *x;
6830     cl.rtIn = -1;
6831     cl.ftIn = -1;
6832     cl.promoCharIn = NULLCHAR;
6833     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6834     if( cl.kind == NormalMove ||
6835         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6836         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6837         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6838       fromX = cl.ff;
6839       fromY = cl.rf;
6840       *x = cl.ft;
6841       *y = cl.rt;
6842       return TRUE;
6843     }
6844     if(cl.kind != ImpossibleMove) return FALSE;
6845     cl.pieceIn = EmptySquare;
6846     cl.rfIn = -1;
6847     cl.ffIn = -1;
6848     cl.rtIn = *y;
6849     cl.ftIn = *x;
6850     cl.promoCharIn = NULLCHAR;
6851     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6852     if( cl.kind == NormalMove ||
6853         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6854         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6855         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6856       fromX = cl.ff;
6857       fromY = cl.rf;
6858       *x = cl.ft;
6859       *y = cl.rt;
6860       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6861       return TRUE;
6862     }
6863     return FALSE;
6864 }
6865
6866 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6867 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6868 int lastLoadGameUseList = FALSE;
6869 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6870 ChessMove lastLoadGameStart = EndOfFile;
6871 int doubleClick;
6872 Boolean addToBookFlag;
6873
6874 void
6875 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6876 {
6877     ChessMove moveType;
6878     ChessSquare pup;
6879     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6880
6881     /* Check if the user is playing in turn.  This is complicated because we
6882        let the user "pick up" a piece before it is his turn.  So the piece he
6883        tried to pick up may have been captured by the time he puts it down!
6884        Therefore we use the color the user is supposed to be playing in this
6885        test, not the color of the piece that is currently on the starting
6886        square---except in EditGame mode, where the user is playing both
6887        sides; fortunately there the capture race can't happen.  (It can
6888        now happen in IcsExamining mode, but that's just too bad.  The user
6889        will get a somewhat confusing message in that case.)
6890        */
6891
6892     switch (gameMode) {
6893       case AnalyzeFile:
6894       case TwoMachinesPlay:
6895       case EndOfGame:
6896       case IcsObserving:
6897       case IcsIdle:
6898         /* We switched into a game mode where moves are not accepted,
6899            perhaps while the mouse button was down. */
6900         return;
6901
6902       case MachinePlaysWhite:
6903         /* User is moving for Black */
6904         if (WhiteOnMove(currentMove)) {
6905             DisplayMoveError(_("It is White's turn"));
6906             return;
6907         }
6908         break;
6909
6910       case MachinePlaysBlack:
6911         /* User is moving for White */
6912         if (!WhiteOnMove(currentMove)) {
6913             DisplayMoveError(_("It is Black's turn"));
6914             return;
6915         }
6916         break;
6917
6918       case PlayFromGameFile:
6919             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6920       case EditGame:
6921       case IcsExamining:
6922       case BeginningOfGame:
6923       case AnalyzeMode:
6924       case Training:
6925         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6926         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6927             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6928             /* User is moving for Black */
6929             if (WhiteOnMove(currentMove)) {
6930                 DisplayMoveError(_("It is White's turn"));
6931                 return;
6932             }
6933         } else {
6934             /* User is moving for White */
6935             if (!WhiteOnMove(currentMove)) {
6936                 DisplayMoveError(_("It is Black's turn"));
6937                 return;
6938             }
6939         }
6940         break;
6941
6942       case IcsPlayingBlack:
6943         /* User is moving for Black */
6944         if (WhiteOnMove(currentMove)) {
6945             if (!appData.premove) {
6946                 DisplayMoveError(_("It is White's turn"));
6947             } else if (toX >= 0 && toY >= 0) {
6948                 premoveToX = toX;
6949                 premoveToY = toY;
6950                 premoveFromX = fromX;
6951                 premoveFromY = fromY;
6952                 premovePromoChar = promoChar;
6953                 gotPremove = 1;
6954                 if (appData.debugMode)
6955                     fprintf(debugFP, "Got premove: fromX %d,"
6956                             "fromY %d, toX %d, toY %d\n",
6957                             fromX, fromY, toX, toY);
6958             }
6959             return;
6960         }
6961         break;
6962
6963       case IcsPlayingWhite:
6964         /* User is moving for White */
6965         if (!WhiteOnMove(currentMove)) {
6966             if (!appData.premove) {
6967                 DisplayMoveError(_("It is Black's turn"));
6968             } else if (toX >= 0 && toY >= 0) {
6969                 premoveToX = toX;
6970                 premoveToY = toY;
6971                 premoveFromX = fromX;
6972                 premoveFromY = fromY;
6973                 premovePromoChar = promoChar;
6974                 gotPremove = 1;
6975                 if (appData.debugMode)
6976                     fprintf(debugFP, "Got premove: fromX %d,"
6977                             "fromY %d, toX %d, toY %d\n",
6978                             fromX, fromY, toX, toY);
6979             }
6980             return;
6981         }
6982         break;
6983
6984       default:
6985         break;
6986
6987       case EditPosition:
6988         /* EditPosition, empty square, or different color piece;
6989            click-click move is possible */
6990         if (toX == -2 || toY == -2) {
6991             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6992             DrawPosition(FALSE, boards[currentMove]);
6993             return;
6994         } else if (toX >= 0 && toY >= 0) {
6995             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6996                 ChessSquare q, p = boards[0][rf][ff];
6997                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6998                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6999                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7000                 if(PieceToChar(q) == '+') gatingPiece = p;
7001             }
7002             boards[0][toY][toX] = boards[0][fromY][fromX];
7003             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7004                 if(boards[0][fromY][0] != EmptySquare) {
7005                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7006                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7007                 }
7008             } else
7009             if(fromX == BOARD_RGHT+1) {
7010                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7011                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7012                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7013                 }
7014             } else
7015             boards[0][fromY][fromX] = gatingPiece;
7016             DrawPosition(FALSE, boards[currentMove]);
7017             return;
7018         }
7019         return;
7020     }
7021
7022     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7023     pup = boards[currentMove][toY][toX];
7024
7025     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7026     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7027          if( pup != EmptySquare ) return;
7028          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7029            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7030                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7031            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7032            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7033            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7034            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7035          fromY = DROP_RANK;
7036     }
7037
7038     /* [HGM] always test for legality, to get promotion info */
7039     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7040                                          fromY, fromX, toY, toX, promoChar);
7041
7042     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7043
7044     /* [HGM] but possibly ignore an IllegalMove result */
7045     if (appData.testLegality) {
7046         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7047             DisplayMoveError(_("Illegal move"));
7048             return;
7049         }
7050     }
7051
7052     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7053         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7054              ClearPremoveHighlights(); // was included
7055         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7056         return;
7057     }
7058
7059     if(addToBookFlag) { // adding moves to book
7060         char buf[MSG_SIZ], move[MSG_SIZ];
7061         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7062         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7063         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7064         AddBookMove(buf);
7065         addToBookFlag = FALSE;
7066         ClearHighlights();
7067         return;
7068     }
7069
7070     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7071 }
7072
7073 /* Common tail of UserMoveEvent and DropMenuEvent */
7074 int
7075 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7076 {
7077     char *bookHit = 0;
7078
7079     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7080         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7081         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7082         if(WhiteOnMove(currentMove)) {
7083             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7084         } else {
7085             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7086         }
7087     }
7088
7089     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7090        move type in caller when we know the move is a legal promotion */
7091     if(moveType == NormalMove && promoChar)
7092         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7093
7094     /* [HGM] <popupFix> The following if has been moved here from
7095        UserMoveEvent(). Because it seemed to belong here (why not allow
7096        piece drops in training games?), and because it can only be
7097        performed after it is known to what we promote. */
7098     if (gameMode == Training) {
7099       /* compare the move played on the board to the next move in the
7100        * game. If they match, display the move and the opponent's response.
7101        * If they don't match, display an error message.
7102        */
7103       int saveAnimate;
7104       Board testBoard;
7105       CopyBoard(testBoard, boards[currentMove]);
7106       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7107
7108       if (CompareBoards(testBoard, boards[currentMove+1])) {
7109         ForwardInner(currentMove+1);
7110
7111         /* Autoplay the opponent's response.
7112          * if appData.animate was TRUE when Training mode was entered,
7113          * the response will be animated.
7114          */
7115         saveAnimate = appData.animate;
7116         appData.animate = animateTraining;
7117         ForwardInner(currentMove+1);
7118         appData.animate = saveAnimate;
7119
7120         /* check for the end of the game */
7121         if (currentMove >= forwardMostMove) {
7122           gameMode = PlayFromGameFile;
7123           ModeHighlight();
7124           SetTrainingModeOff();
7125           DisplayInformation(_("End of game"));
7126         }
7127       } else {
7128         DisplayError(_("Incorrect move"), 0);
7129       }
7130       return 1;
7131     }
7132
7133   /* Ok, now we know that the move is good, so we can kill
7134      the previous line in Analysis Mode */
7135   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7136                                 && currentMove < forwardMostMove) {
7137     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7138     else forwardMostMove = currentMove;
7139   }
7140
7141   ClearMap();
7142
7143   /* If we need the chess program but it's dead, restart it */
7144   ResurrectChessProgram();
7145
7146   /* A user move restarts a paused game*/
7147   if (pausing)
7148     PauseEvent();
7149
7150   thinkOutput[0] = NULLCHAR;
7151
7152   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7153
7154   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7155     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7156     return 1;
7157   }
7158
7159   if (gameMode == BeginningOfGame) {
7160     if (appData.noChessProgram) {
7161       gameMode = EditGame;
7162       SetGameInfo();
7163     } else {
7164       char buf[MSG_SIZ];
7165       gameMode = MachinePlaysBlack;
7166       StartClocks();
7167       SetGameInfo();
7168       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7169       DisplayTitle(buf);
7170       if (first.sendName) {
7171         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7172         SendToProgram(buf, &first);
7173       }
7174       StartClocks();
7175     }
7176     ModeHighlight();
7177   }
7178
7179   /* Relay move to ICS or chess engine */
7180   if (appData.icsActive) {
7181     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7182         gameMode == IcsExamining) {
7183       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7184         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7185         SendToICS("draw ");
7186         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7187       }
7188       // also send plain move, in case ICS does not understand atomic claims
7189       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7190       ics_user_moved = 1;
7191     }
7192   } else {
7193     if (first.sendTime && (gameMode == BeginningOfGame ||
7194                            gameMode == MachinePlaysWhite ||
7195                            gameMode == MachinePlaysBlack)) {
7196       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7197     }
7198     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7199          // [HGM] book: if program might be playing, let it use book
7200         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7201         first.maybeThinking = TRUE;
7202     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7203         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7204         SendBoard(&first, currentMove+1);
7205         if(second.analyzing) {
7206             if(!second.useSetboard) SendToProgram("undo\n", &second);
7207             SendBoard(&second, currentMove+1);
7208         }
7209     } else {
7210         SendMoveToProgram(forwardMostMove-1, &first);
7211         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7212     }
7213     if (currentMove == cmailOldMove + 1) {
7214       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7215     }
7216   }
7217
7218   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7219
7220   switch (gameMode) {
7221   case EditGame:
7222     if(appData.testLegality)
7223     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7224     case MT_NONE:
7225     case MT_CHECK:
7226       break;
7227     case MT_CHECKMATE:
7228     case MT_STAINMATE:
7229       if (WhiteOnMove(currentMove)) {
7230         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7231       } else {
7232         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7233       }
7234       break;
7235     case MT_STALEMATE:
7236       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7237       break;
7238     }
7239     break;
7240
7241   case MachinePlaysBlack:
7242   case MachinePlaysWhite:
7243     /* disable certain menu options while machine is thinking */
7244     SetMachineThinkingEnables();
7245     break;
7246
7247   default:
7248     break;
7249   }
7250
7251   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7252   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7253
7254   if(bookHit) { // [HGM] book: simulate book reply
7255         static char bookMove[MSG_SIZ]; // a bit generous?
7256
7257         programStats.nodes = programStats.depth = programStats.time =
7258         programStats.score = programStats.got_only_move = 0;
7259         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7260
7261         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7262         strcat(bookMove, bookHit);
7263         HandleMachineMove(bookMove, &first);
7264   }
7265   return 1;
7266 }
7267
7268 void
7269 MarkByFEN(char *fen)
7270 {
7271         int r, f;
7272         if(!appData.markers || !appData.highlightDragging) return;
7273         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7274         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7275         while(*fen) {
7276             int s = 0;
7277             marker[r][f] = 0;
7278             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7279             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7280             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7281             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7282             if(*fen == 'T') marker[r][f++] = 0; else
7283             if(*fen == 'Y') marker[r][f++] = 1; else
7284             if(*fen == 'G') marker[r][f++] = 3; else
7285             if(*fen == 'B') marker[r][f++] = 4; else
7286             if(*fen == 'C') marker[r][f++] = 5; else
7287             if(*fen == 'M') marker[r][f++] = 6; else
7288             if(*fen == 'W') marker[r][f++] = 7; else
7289             if(*fen == 'D') marker[r][f++] = 8; else
7290             if(*fen == 'R') marker[r][f++] = 2; else {
7291                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7292               f += s; fen -= s>0;
7293             }
7294             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7295             if(r < 0) break;
7296             fen++;
7297         }
7298         DrawPosition(TRUE, NULL);
7299 }
7300
7301 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7302
7303 void
7304 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7305 {
7306     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7307     Markers *m = (Markers *) closure;
7308     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7309         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7310                          || kind == WhiteCapturesEnPassant
7311                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7312     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7313 }
7314
7315 static int hoverSavedValid;
7316
7317 void
7318 MarkTargetSquares (int clear)
7319 {
7320   int x, y, sum=0;
7321   if(clear) { // no reason to ever suppress clearing
7322     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7323     hoverSavedValid = 0;
7324     if(!sum) return; // nothing was cleared,no redraw needed
7325   } else {
7326     int capt = 0;
7327     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7328        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7329     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7330     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7331       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7332       if(capt)
7333       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7334     }
7335   }
7336   DrawPosition(FALSE, NULL);
7337 }
7338
7339 int
7340 Explode (Board board, int fromX, int fromY, int toX, int toY)
7341 {
7342     if(gameInfo.variant == VariantAtomic &&
7343        (board[toY][toX] != EmptySquare ||                     // capture?
7344         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7345                          board[fromY][fromX] == BlackPawn   )
7346       )) {
7347         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7348         return TRUE;
7349     }
7350     return FALSE;
7351 }
7352
7353 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7354
7355 int
7356 CanPromote (ChessSquare piece, int y)
7357 {
7358         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7359         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7360         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7361         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7362            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7363            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7364          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7365         return (piece == BlackPawn && y <= zone ||
7366                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7367                 piece == BlackLance && y <= zone ||
7368                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7369 }
7370
7371 void
7372 HoverEvent (int xPix, int yPix, int x, int y)
7373 {
7374         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7375         int r, f;
7376         if(!first.highlight) return;
7377         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7378         if(x == oldX && y == oldY) return; // only do something if we enter new square
7379         oldFromX = fromX; oldFromY = fromY;
7380         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7381           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7382             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7383           hoverSavedValid = 1;
7384         } else if(oldX != x || oldY != y) {
7385           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7386           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7387           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7388             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7389           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7390             char buf[MSG_SIZ];
7391             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7392             SendToProgram(buf, &first);
7393           }
7394           oldX = x; oldY = y;
7395 //        SetHighlights(fromX, fromY, x, y);
7396         }
7397 }
7398
7399 void ReportClick(char *action, int x, int y)
7400 {
7401         char buf[MSG_SIZ]; // Inform engine of what user does
7402         int r, f;
7403         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7404           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7405             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7406         if(!first.highlight || gameMode == EditPosition) return;
7407         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7408         SendToProgram(buf, &first);
7409 }
7410
7411 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7412
7413 void
7414 LeftClick (ClickType clickType, int xPix, int yPix)
7415 {
7416     int x, y;
7417     Boolean saveAnimate;
7418     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7419     char promoChoice = NULLCHAR;
7420     ChessSquare piece;
7421     static TimeMark lastClickTime, prevClickTime;
7422
7423     x = EventToSquare(xPix, BOARD_WIDTH);
7424     y = EventToSquare(yPix, BOARD_HEIGHT);
7425     if (!flipView && y >= 0) {
7426         y = BOARD_HEIGHT - 1 - y;
7427     }
7428     if (flipView && x >= 0) {
7429         x = BOARD_WIDTH - 1 - x;
7430     }
7431
7432     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7433         static int dummy;
7434         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7435         right = TRUE;
7436         return;
7437     }
7438
7439     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7440
7441     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7442
7443     if (clickType == Press) ErrorPopDown();
7444     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7445
7446     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7447         defaultPromoChoice = promoSweep;
7448         promoSweep = EmptySquare;   // terminate sweep
7449         promoDefaultAltered = TRUE;
7450         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7451     }
7452
7453     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7454         if(clickType == Release) return; // ignore upclick of click-click destination
7455         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7456         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7457         if(gameInfo.holdingsWidth &&
7458                 (WhiteOnMove(currentMove)
7459                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7460                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7461             // click in right holdings, for determining promotion piece
7462             ChessSquare p = boards[currentMove][y][x];
7463             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7464             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7465             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7466                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7467                 fromX = fromY = -1;
7468                 return;
7469             }
7470         }
7471         DrawPosition(FALSE, boards[currentMove]);
7472         return;
7473     }
7474
7475     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7476     if(clickType == Press
7477             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7478               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7479               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7480         return;
7481
7482     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7483         // could be static click on premove from-square: abort premove
7484         gotPremove = 0;
7485         ClearPremoveHighlights();
7486     }
7487
7488     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7489         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7490
7491     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7492         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7493                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7494         defaultPromoChoice = DefaultPromoChoice(side);
7495     }
7496
7497     autoQueen = appData.alwaysPromoteToQueen;
7498
7499     if (fromX == -1) {
7500       int originalY = y;
7501       gatingPiece = EmptySquare;
7502       if (clickType != Press) {
7503         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7504             DragPieceEnd(xPix, yPix); dragging = 0;
7505             DrawPosition(FALSE, NULL);
7506         }
7507         return;
7508       }
7509       doubleClick = FALSE;
7510       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7511         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7512       }
7513       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7514       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7515          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7516          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7517             /* First square */
7518             if (OKToStartUserMove(fromX, fromY)) {
7519                 second = 0;
7520                 ReportClick("lift", x, y);
7521                 MarkTargetSquares(0);
7522                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7523                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7524                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7525                     promoSweep = defaultPromoChoice;
7526                     selectFlag = 0; lastX = xPix; lastY = yPix;
7527                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7528                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7529                 }
7530                 if (appData.highlightDragging) {
7531                     SetHighlights(fromX, fromY, -1, -1);
7532                 } else {
7533                     ClearHighlights();
7534                 }
7535             } else fromX = fromY = -1;
7536             return;
7537         }
7538     }
7539 printf("to click %d,%d\n",x,y);
7540     /* fromX != -1 */
7541     if (clickType == Press && gameMode != EditPosition) {
7542         ChessSquare fromP;
7543         ChessSquare toP;
7544         int frc;
7545
7546         // ignore off-board to clicks
7547         if(y < 0 || x < 0) return;
7548
7549         /* Check if clicking again on the same color piece */
7550         fromP = boards[currentMove][fromY][fromX];
7551         toP = boards[currentMove][y][x];
7552         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7553         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7554             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7555            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7556              WhitePawn <= toP && toP <= WhiteKing &&
7557              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7558              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7559             (BlackPawn <= fromP && fromP <= BlackKing &&
7560              BlackPawn <= toP && toP <= BlackKing &&
7561              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7562              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7563             /* Clicked again on same color piece -- changed his mind */
7564             second = (x == fromX && y == fromY);
7565             killX = killY = -1;
7566             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7567                 second = FALSE; // first double-click rather than scond click
7568                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7569             }
7570             promoDefaultAltered = FALSE;
7571             MarkTargetSquares(1);
7572            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7573             if (appData.highlightDragging) {
7574                 SetHighlights(x, y, -1, -1);
7575             } else {
7576                 ClearHighlights();
7577             }
7578             if (OKToStartUserMove(x, y)) {
7579                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7580                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7581                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7582                  gatingPiece = boards[currentMove][fromY][fromX];
7583                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7584                 fromX = x;
7585                 fromY = y; dragging = 1;
7586                 if(!second) ReportClick("lift", x, y);
7587                 MarkTargetSquares(0);
7588                 DragPieceBegin(xPix, yPix, FALSE);
7589                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7590                     promoSweep = defaultPromoChoice;
7591                     selectFlag = 0; lastX = xPix; lastY = yPix;
7592                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7593                 }
7594             }
7595            }
7596            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7597            second = FALSE;
7598         }
7599         // ignore clicks on holdings
7600         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7601     }
7602 printf("A type=%d\n",clickType);
7603
7604     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7605         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7606         return;
7607     }
7608
7609     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7610         DragPieceEnd(xPix, yPix); dragging = 0;
7611         if(clearFlag) {
7612             // a deferred attempt to click-click move an empty square on top of a piece
7613             boards[currentMove][y][x] = EmptySquare;
7614             ClearHighlights();
7615             DrawPosition(FALSE, boards[currentMove]);
7616             fromX = fromY = -1; clearFlag = 0;
7617             return;
7618         }
7619         if (appData.animateDragging) {
7620             /* Undo animation damage if any */
7621             DrawPosition(FALSE, NULL);
7622         }
7623         if (second) {
7624             /* Second up/down in same square; just abort move */
7625             second = 0;
7626             fromX = fromY = -1;
7627             gatingPiece = EmptySquare;
7628             MarkTargetSquares(1);
7629             ClearHighlights();
7630             gotPremove = 0;
7631             ClearPremoveHighlights();
7632         } else {
7633             /* First upclick in same square; start click-click mode */
7634             SetHighlights(x, y, -1, -1);
7635         }
7636         return;
7637     }
7638
7639     clearFlag = 0;
7640 printf("B\n");
7641     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7642        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7643         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7644         DisplayMessage(_("only marked squares are legal"),"");
7645         DrawPosition(TRUE, NULL);
7646         return; // ignore to-click
7647     }
7648 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7649     /* we now have a different from- and (possibly off-board) to-square */
7650     /* Completed move */
7651     if(!sweepSelecting) {
7652         toX = x;
7653         toY = y;
7654     }
7655
7656     piece = boards[currentMove][fromY][fromX];
7657
7658     saveAnimate = appData.animate;
7659     if (clickType == Press) {
7660         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7661         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7662             // must be Edit Position mode with empty-square selected
7663             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7664             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7665             return;
7666         }
7667         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7668             return;
7669         }
7670         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7671             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7672         } else
7673         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7674         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7675           if(appData.sweepSelect) {
7676             promoSweep = defaultPromoChoice;
7677             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7678             selectFlag = 0; lastX = xPix; lastY = yPix;
7679             Sweep(0); // Pawn that is going to promote: preview promotion piece
7680             sweepSelecting = 1;
7681             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7682             MarkTargetSquares(1);
7683           }
7684           return; // promo popup appears on up-click
7685         }
7686         /* Finish clickclick move */
7687         if (appData.animate || appData.highlightLastMove) {
7688             SetHighlights(fromX, fromY, toX, toY);
7689         } else {
7690             ClearHighlights();
7691         }
7692     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7693         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7694         if (appData.animate || appData.highlightLastMove) {
7695             SetHighlights(fromX, fromY, toX, toY);
7696         } else {
7697             ClearHighlights();
7698         }
7699     } else {
7700 #if 0
7701 // [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
7702         /* Finish drag move */
7703         if (appData.highlightLastMove) {
7704             SetHighlights(fromX, fromY, toX, toY);
7705         } else {
7706             ClearHighlights();
7707         }
7708 #endif
7709         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7710         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7711           dragging *= 2;            // flag button-less dragging if we are dragging
7712           MarkTargetSquares(1);
7713           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7714           else {
7715             kill2X = killX; kill2Y = killY;
7716             killX = x; killY = y;     //remeber this square as intermediate
7717             ReportClick("put", x, y); // and inform engine
7718             ReportClick("lift", x, y);
7719             MarkTargetSquares(0);
7720             return;
7721           }
7722         }
7723         DragPieceEnd(xPix, yPix); dragging = 0;
7724         /* Don't animate move and drag both */
7725         appData.animate = FALSE;
7726     }
7727
7728     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7729     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7730         ChessSquare piece = boards[currentMove][fromY][fromX];
7731         if(gameMode == EditPosition && piece != EmptySquare &&
7732            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7733             int n;
7734
7735             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7736                 n = PieceToNumber(piece - (int)BlackPawn);
7737                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7738                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7739                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7740             } else
7741             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7742                 n = PieceToNumber(piece);
7743                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7744                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7745                 boards[currentMove][n][BOARD_WIDTH-2]++;
7746             }
7747             boards[currentMove][fromY][fromX] = EmptySquare;
7748         }
7749         ClearHighlights();
7750         fromX = fromY = -1;
7751         MarkTargetSquares(1);
7752         DrawPosition(TRUE, boards[currentMove]);
7753         return;
7754     }
7755
7756     // off-board moves should not be highlighted
7757     if(x < 0 || y < 0) ClearHighlights();
7758     else ReportClick("put", x, y);
7759
7760     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7761
7762     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7763
7764     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7765         SetHighlights(fromX, fromY, toX, toY);
7766         MarkTargetSquares(1);
7767         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7768             // [HGM] super: promotion to captured piece selected from holdings
7769             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7770             promotionChoice = TRUE;
7771             // kludge follows to temporarily execute move on display, without promoting yet
7772             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7773             boards[currentMove][toY][toX] = p;
7774             DrawPosition(FALSE, boards[currentMove]);
7775             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7776             boards[currentMove][toY][toX] = q;
7777             DisplayMessage("Click in holdings to choose piece", "");
7778             return;
7779         }
7780         PromotionPopUp(promoChoice);
7781     } else {
7782         int oldMove = currentMove;
7783         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7784         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7785         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7786         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7787            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7788             DrawPosition(TRUE, boards[currentMove]);
7789         MarkTargetSquares(1);
7790         fromX = fromY = -1;
7791     }
7792     appData.animate = saveAnimate;
7793     if (appData.animate || appData.animateDragging) {
7794         /* Undo animation damage if needed */
7795         DrawPosition(FALSE, NULL);
7796     }
7797 }
7798
7799 int
7800 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7801 {   // front-end-free part taken out of PieceMenuPopup
7802     int whichMenu; int xSqr, ySqr;
7803
7804     if(seekGraphUp) { // [HGM] seekgraph
7805         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7806         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7807         return -2;
7808     }
7809
7810     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7811          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7812         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7813         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7814         if(action == Press)   {
7815             originalFlip = flipView;
7816             flipView = !flipView; // temporarily flip board to see game from partners perspective
7817             DrawPosition(TRUE, partnerBoard);
7818             DisplayMessage(partnerStatus, "");
7819             partnerUp = TRUE;
7820         } else if(action == Release) {
7821             flipView = originalFlip;
7822             DrawPosition(TRUE, boards[currentMove]);
7823             partnerUp = FALSE;
7824         }
7825         return -2;
7826     }
7827
7828     xSqr = EventToSquare(x, BOARD_WIDTH);
7829     ySqr = EventToSquare(y, BOARD_HEIGHT);
7830     if (action == Release) {
7831         if(pieceSweep != EmptySquare) {
7832             EditPositionMenuEvent(pieceSweep, toX, toY);
7833             pieceSweep = EmptySquare;
7834         } else UnLoadPV(); // [HGM] pv
7835     }
7836     if (action != Press) return -2; // return code to be ignored
7837     switch (gameMode) {
7838       case IcsExamining:
7839         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7840       case EditPosition:
7841         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7842         if (xSqr < 0 || ySqr < 0) return -1;
7843         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7844         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7845         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7846         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7847         NextPiece(0);
7848         return 2; // grab
7849       case IcsObserving:
7850         if(!appData.icsEngineAnalyze) return -1;
7851       case IcsPlayingWhite:
7852       case IcsPlayingBlack:
7853         if(!appData.zippyPlay) goto noZip;
7854       case AnalyzeMode:
7855       case AnalyzeFile:
7856       case MachinePlaysWhite:
7857       case MachinePlaysBlack:
7858       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7859         if (!appData.dropMenu) {
7860           LoadPV(x, y);
7861           return 2; // flag front-end to grab mouse events
7862         }
7863         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7864            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7865       case EditGame:
7866       noZip:
7867         if (xSqr < 0 || ySqr < 0) return -1;
7868         if (!appData.dropMenu || appData.testLegality &&
7869             gameInfo.variant != VariantBughouse &&
7870             gameInfo.variant != VariantCrazyhouse) return -1;
7871         whichMenu = 1; // drop menu
7872         break;
7873       default:
7874         return -1;
7875     }
7876
7877     if (((*fromX = xSqr) < 0) ||
7878         ((*fromY = ySqr) < 0)) {
7879         *fromX = *fromY = -1;
7880         return -1;
7881     }
7882     if (flipView)
7883       *fromX = BOARD_WIDTH - 1 - *fromX;
7884     else
7885       *fromY = BOARD_HEIGHT - 1 - *fromY;
7886
7887     return whichMenu;
7888 }
7889
7890 void
7891 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7892 {
7893 //    char * hint = lastHint;
7894     FrontEndProgramStats stats;
7895
7896     stats.which = cps == &first ? 0 : 1;
7897     stats.depth = cpstats->depth;
7898     stats.nodes = cpstats->nodes;
7899     stats.score = cpstats->score;
7900     stats.time = cpstats->time;
7901     stats.pv = cpstats->movelist;
7902     stats.hint = lastHint;
7903     stats.an_move_index = 0;
7904     stats.an_move_count = 0;
7905
7906     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7907         stats.hint = cpstats->move_name;
7908         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7909         stats.an_move_count = cpstats->nr_moves;
7910     }
7911
7912     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
7913
7914     SetProgramStats( &stats );
7915 }
7916
7917 void
7918 ClearEngineOutputPane (int which)
7919 {
7920     static FrontEndProgramStats dummyStats;
7921     dummyStats.which = which;
7922     dummyStats.pv = "#";
7923     SetProgramStats( &dummyStats );
7924 }
7925
7926 #define MAXPLAYERS 500
7927
7928 char *
7929 TourneyStandings (int display)
7930 {
7931     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7932     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7933     char result, *p, *names[MAXPLAYERS];
7934
7935     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7936         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7937     names[0] = p = strdup(appData.participants);
7938     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7939
7940     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7941
7942     while(result = appData.results[nr]) {
7943         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7944         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7945         wScore = bScore = 0;
7946         switch(result) {
7947           case '+': wScore = 2; break;
7948           case '-': bScore = 2; break;
7949           case '=': wScore = bScore = 1; break;
7950           case ' ':
7951           case '*': return strdup("busy"); // tourney not finished
7952         }
7953         score[w] += wScore;
7954         score[b] += bScore;
7955         games[w]++;
7956         games[b]++;
7957         nr++;
7958     }
7959     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7960     for(w=0; w<nPlayers; w++) {
7961         bScore = -1;
7962         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7963         ranking[w] = b; points[w] = bScore; score[b] = -2;
7964     }
7965     p = malloc(nPlayers*34+1);
7966     for(w=0; w<nPlayers && w<display; w++)
7967         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7968     free(names[0]);
7969     return p;
7970 }
7971
7972 void
7973 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7974 {       // count all piece types
7975         int p, f, r;
7976         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7977         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7978         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7979                 p = board[r][f];
7980                 pCnt[p]++;
7981                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7982                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7983                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7984                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7985                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7986                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7987         }
7988 }
7989
7990 int
7991 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7992 {
7993         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7994         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7995
7996         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7997         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7998         if(myPawns == 2 && nMine == 3) // KPP
7999             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8000         if(myPawns == 1 && nMine == 2) // KP
8001             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8002         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8003             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8004         if(myPawns) return FALSE;
8005         if(pCnt[WhiteRook+side])
8006             return pCnt[BlackRook-side] ||
8007                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8008                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8009                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8010         if(pCnt[WhiteCannon+side]) {
8011             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8012             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8013         }
8014         if(pCnt[WhiteKnight+side])
8015             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8016         return FALSE;
8017 }
8018
8019 int
8020 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8021 {
8022         VariantClass v = gameInfo.variant;
8023
8024         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8025         if(v == VariantShatranj) return TRUE; // always winnable through baring
8026         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8027         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8028
8029         if(v == VariantXiangqi) {
8030                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8031
8032                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8033                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8034                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8035                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8036                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8037                 if(stale) // we have at least one last-rank P plus perhaps C
8038                     return majors // KPKX
8039                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8040                 else // KCA*E*
8041                     return pCnt[WhiteFerz+side] // KCAK
8042                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8043                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8044                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8045
8046         } else if(v == VariantKnightmate) {
8047                 if(nMine == 1) return FALSE;
8048                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8049         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8050                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8051
8052                 if(nMine == 1) return FALSE; // bare King
8053                 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
8054                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8055                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8056                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8057                 if(pCnt[WhiteKnight+side])
8058                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8059                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8060                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8061                 if(nBishops)
8062                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8063                 if(pCnt[WhiteAlfil+side])
8064                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8065                 if(pCnt[WhiteWazir+side])
8066                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8067         }
8068
8069         return TRUE;
8070 }
8071
8072 int
8073 CompareWithRights (Board b1, Board b2)
8074 {
8075     int rights = 0;
8076     if(!CompareBoards(b1, b2)) return FALSE;
8077     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8078     /* compare castling rights */
8079     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8080            rights++; /* King lost rights, while rook still had them */
8081     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8082         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8083            rights++; /* but at least one rook lost them */
8084     }
8085     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8086            rights++;
8087     if( b1[CASTLING][5] != NoRights ) {
8088         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8089            rights++;
8090     }
8091     return rights == 0;
8092 }
8093
8094 int
8095 Adjudicate (ChessProgramState *cps)
8096 {       // [HGM] some adjudications useful with buggy engines
8097         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8098         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8099         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8100         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8101         int k, drop, count = 0; static int bare = 1;
8102         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8103         Boolean canAdjudicate = !appData.icsActive;
8104
8105         // most tests only when we understand the game, i.e. legality-checking on
8106             if( appData.testLegality )
8107             {   /* [HGM] Some more adjudications for obstinate engines */
8108                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8109                 static int moveCount = 6;
8110                 ChessMove result;
8111                 char *reason = NULL;
8112
8113                 /* Count what is on board. */
8114                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8115
8116                 /* Some material-based adjudications that have to be made before stalemate test */
8117                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8118                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8119                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8120                      if(canAdjudicate && appData.checkMates) {
8121                          if(engineOpponent)
8122                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8123                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8124                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8125                          return 1;
8126                      }
8127                 }
8128
8129                 /* Bare King in Shatranj (loses) or Losers (wins) */
8130                 if( nrW == 1 || nrB == 1) {
8131                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8132                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8133                      if(canAdjudicate && appData.checkMates) {
8134                          if(engineOpponent)
8135                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8136                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8137                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8138                          return 1;
8139                      }
8140                   } else
8141                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8142                   {    /* bare King */
8143                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8144                         if(canAdjudicate && appData.checkMates) {
8145                             /* but only adjudicate if adjudication enabled */
8146                             if(engineOpponent)
8147                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8148                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8149                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8150                             return 1;
8151                         }
8152                   }
8153                 } else bare = 1;
8154
8155
8156             // don't wait for engine to announce game end if we can judge ourselves
8157             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8158               case MT_CHECK:
8159                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8160                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8161                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8162                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8163                             checkCnt++;
8164                         if(checkCnt >= 2) {
8165                             reason = "Xboard adjudication: 3rd check";
8166                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8167                             break;
8168                         }
8169                     }
8170                 }
8171               case MT_NONE:
8172               default:
8173                 break;
8174               case MT_STEALMATE:
8175               case MT_STALEMATE:
8176               case MT_STAINMATE:
8177                 reason = "Xboard adjudication: Stalemate";
8178                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8179                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8180                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8181                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8182                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8183                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8184                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8185                                                                         EP_CHECKMATE : EP_WINS);
8186                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8187                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8188                 }
8189                 break;
8190               case MT_CHECKMATE:
8191                 reason = "Xboard adjudication: Checkmate";
8192                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8193                 if(gameInfo.variant == VariantShogi) {
8194                     if(forwardMostMove > backwardMostMove
8195                        && moveList[forwardMostMove-1][1] == '@'
8196                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8197                         reason = "XBoard adjudication: pawn-drop mate";
8198                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8199                     }
8200                 }
8201                 break;
8202             }
8203
8204                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8205                     case EP_STALEMATE:
8206                         result = GameIsDrawn; break;
8207                     case EP_CHECKMATE:
8208                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8209                     case EP_WINS:
8210                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8211                     default:
8212                         result = EndOfFile;
8213                 }
8214                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8215                     if(engineOpponent)
8216                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8217                     GameEnds( result, reason, GE_XBOARD );
8218                     return 1;
8219                 }
8220
8221                 /* Next absolutely insufficient mating material. */
8222                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8223                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8224                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8225
8226                      /* always flag draws, for judging claims */
8227                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8228
8229                      if(canAdjudicate && appData.materialDraws) {
8230                          /* but only adjudicate them if adjudication enabled */
8231                          if(engineOpponent) {
8232                            SendToProgram("force\n", engineOpponent); // suppress reply
8233                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8234                          }
8235                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8236                          return 1;
8237                      }
8238                 }
8239
8240                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8241                 if(gameInfo.variant == VariantXiangqi ?
8242                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8243                  : nrW + nrB == 4 &&
8244                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8245                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8246                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8247                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8248                    ) ) {
8249                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8250                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8251                           if(engineOpponent) {
8252                             SendToProgram("force\n", engineOpponent); // suppress reply
8253                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8254                           }
8255                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8256                           return 1;
8257                      }
8258                 } else moveCount = 6;
8259             }
8260
8261         // Repetition draws and 50-move rule can be applied independently of legality testing
8262
8263                 /* Check for rep-draws */
8264                 count = 0;
8265                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8266                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8267                 for(k = forwardMostMove-2;
8268                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8269                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8270                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8271                     k-=2)
8272                 {   int rights=0;
8273                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8274                         /* compare castling rights */
8275                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8276                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8277                                 rights++; /* King lost rights, while rook still had them */
8278                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8279                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8280                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8281                                    rights++; /* but at least one rook lost them */
8282                         }
8283                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8284                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8285                                 rights++;
8286                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8287                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8288                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8289                                    rights++;
8290                         }
8291                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8292                             && appData.drawRepeats > 1) {
8293                              /* adjudicate after user-specified nr of repeats */
8294                              int result = GameIsDrawn;
8295                              char *details = "XBoard adjudication: repetition draw";
8296                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8297                                 // [HGM] xiangqi: check for forbidden perpetuals
8298                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8299                                 for(m=forwardMostMove; m>k; m-=2) {
8300                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8301                                         ourPerpetual = 0; // the current mover did not always check
8302                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8303                                         hisPerpetual = 0; // the opponent did not always check
8304                                 }
8305                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8306                                                                         ourPerpetual, hisPerpetual);
8307                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8308                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8309                                     details = "Xboard adjudication: perpetual checking";
8310                                 } else
8311                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8312                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8313                                 } else
8314                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8315                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8316                                         result = BlackWins;
8317                                         details = "Xboard adjudication: repetition";
8318                                     }
8319                                 } else // it must be XQ
8320                                 // Now check for perpetual chases
8321                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8322                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8323                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8324                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8325                                         static char resdet[MSG_SIZ];
8326                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8327                                         details = resdet;
8328                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8329                                     } else
8330                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8331                                         break; // Abort repetition-checking loop.
8332                                 }
8333                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8334                              }
8335                              if(engineOpponent) {
8336                                SendToProgram("force\n", engineOpponent); // suppress reply
8337                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8338                              }
8339                              GameEnds( result, details, GE_XBOARD );
8340                              return 1;
8341                         }
8342                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8343                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8344                     }
8345                 }
8346
8347                 /* Now we test for 50-move draws. Determine ply count */
8348                 count = forwardMostMove;
8349                 /* look for last irreversble move */
8350                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8351                     count--;
8352                 /* if we hit starting position, add initial plies */
8353                 if( count == backwardMostMove )
8354                     count -= initialRulePlies;
8355                 count = forwardMostMove - count;
8356                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8357                         // adjust reversible move counter for checks in Xiangqi
8358                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8359                         if(i < backwardMostMove) i = backwardMostMove;
8360                         while(i <= forwardMostMove) {
8361                                 lastCheck = inCheck; // check evasion does not count
8362                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8363                                 if(inCheck || lastCheck) count--; // check does not count
8364                                 i++;
8365                         }
8366                 }
8367                 if( count >= 100)
8368                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8369                          /* this is used to judge if draw claims are legal */
8370                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8371                          if(engineOpponent) {
8372                            SendToProgram("force\n", engineOpponent); // suppress reply
8373                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8374                          }
8375                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8376                          return 1;
8377                 }
8378
8379                 /* if draw offer is pending, treat it as a draw claim
8380                  * when draw condition present, to allow engines a way to
8381                  * claim draws before making their move to avoid a race
8382                  * condition occurring after their move
8383                  */
8384                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8385                          char *p = NULL;
8386                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8387                              p = "Draw claim: 50-move rule";
8388                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8389                              p = "Draw claim: 3-fold repetition";
8390                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8391                              p = "Draw claim: insufficient mating material";
8392                          if( p != NULL && canAdjudicate) {
8393                              if(engineOpponent) {
8394                                SendToProgram("force\n", engineOpponent); // suppress reply
8395                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8396                              }
8397                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8398                              return 1;
8399                          }
8400                 }
8401
8402                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8403                     if(engineOpponent) {
8404                       SendToProgram("force\n", engineOpponent); // suppress reply
8405                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8406                     }
8407                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8408                     return 1;
8409                 }
8410         return 0;
8411 }
8412
8413 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8414 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8415 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8416
8417 static int
8418 BitbaseProbe ()
8419 {
8420     int pieces[10], squares[10], cnt=0, r, f, res;
8421     static int loaded;
8422     static PPROBE_EGBB probeBB;
8423     if(!appData.testLegality) return 10;
8424     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8425     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8426     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8427     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8428         ChessSquare piece = boards[forwardMostMove][r][f];
8429         int black = (piece >= BlackPawn);
8430         int type = piece - black*BlackPawn;
8431         if(piece == EmptySquare) continue;
8432         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8433         if(type == WhiteKing) type = WhiteQueen + 1;
8434         type = egbbCode[type];
8435         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8436         pieces[cnt] = type + black*6;
8437         if(++cnt > 5) return 11;
8438     }
8439     pieces[cnt] = squares[cnt] = 0;
8440     // probe EGBB
8441     if(loaded == 2) return 13; // loading failed before
8442     if(loaded == 0) {
8443         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8444         HMODULE lib;
8445         PLOAD_EGBB loadBB;
8446         loaded = 2; // prepare for failure
8447         if(!path) return 13; // no egbb installed
8448         strncpy(buf, path + 8, MSG_SIZ);
8449         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8450         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8451         lib = LoadLibrary(buf);
8452         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8453         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8454         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8455         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8456         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8457         loaded = 1; // success!
8458     }
8459     res = probeBB(forwardMostMove & 1, pieces, squares);
8460     return res > 0 ? 1 : res < 0 ? -1 : 0;
8461 }
8462
8463 char *
8464 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8465 {   // [HGM] book: this routine intercepts moves to simulate book replies
8466     char *bookHit = NULL;
8467
8468     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8469         char buf[MSG_SIZ];
8470         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8471         SendToProgram(buf, cps);
8472     }
8473     //first determine if the incoming move brings opponent into his book
8474     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8475         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8476     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8477     if(bookHit != NULL && !cps->bookSuspend) {
8478         // make sure opponent is not going to reply after receiving move to book position
8479         SendToProgram("force\n", cps);
8480         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8481     }
8482     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8483     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8484     // now arrange restart after book miss
8485     if(bookHit) {
8486         // after a book hit we never send 'go', and the code after the call to this routine
8487         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8488         char buf[MSG_SIZ], *move = bookHit;
8489         if(cps->useSAN) {
8490             int fromX, fromY, toX, toY;
8491             char promoChar;
8492             ChessMove moveType;
8493             move = buf + 30;
8494             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8495                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8496                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8497                                     PosFlags(forwardMostMove),
8498                                     fromY, fromX, toY, toX, promoChar, move);
8499             } else {
8500                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8501                 bookHit = NULL;
8502             }
8503         }
8504         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8505         SendToProgram(buf, cps);
8506         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8507     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8508         SendToProgram("go\n", cps);
8509         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8510     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8511         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8512             SendToProgram("go\n", cps);
8513         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8514     }
8515     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8516 }
8517
8518 int
8519 LoadError (char *errmess, ChessProgramState *cps)
8520 {   // unloads engine and switches back to -ncp mode if it was first
8521     if(cps->initDone) return FALSE;
8522     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8523     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8524     cps->pr = NoProc;
8525     if(cps == &first) {
8526         appData.noChessProgram = TRUE;
8527         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8528         gameMode = BeginningOfGame; ModeHighlight();
8529         SetNCPMode();
8530     }
8531     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8532     DisplayMessage("", ""); // erase waiting message
8533     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8534     return TRUE;
8535 }
8536
8537 char *savedMessage;
8538 ChessProgramState *savedState;
8539 void
8540 DeferredBookMove (void)
8541 {
8542         if(savedState->lastPing != savedState->lastPong)
8543                     ScheduleDelayedEvent(DeferredBookMove, 10);
8544         else
8545         HandleMachineMove(savedMessage, savedState);
8546 }
8547
8548 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8549 static ChessProgramState *stalledEngine;
8550 static char stashedInputMove[MSG_SIZ];
8551
8552 void
8553 HandleMachineMove (char *message, ChessProgramState *cps)
8554 {
8555     static char firstLeg[20];
8556     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8557     char realname[MSG_SIZ];
8558     int fromX, fromY, toX, toY;
8559     ChessMove moveType;
8560     char promoChar, roar;
8561     char *p, *pv=buf1;
8562     int machineWhite, oldError;
8563     char *bookHit;
8564
8565     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8566         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8567         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8568             DisplayError(_("Invalid pairing from pairing engine"), 0);
8569             return;
8570         }
8571         pairingReceived = 1;
8572         NextMatchGame();
8573         return; // Skim the pairing messages here.
8574     }
8575
8576     oldError = cps->userError; cps->userError = 0;
8577
8578 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8579     /*
8580      * Kludge to ignore BEL characters
8581      */
8582     while (*message == '\007') message++;
8583
8584     /*
8585      * [HGM] engine debug message: ignore lines starting with '#' character
8586      */
8587     if(cps->debug && *message == '#') return;
8588
8589     /*
8590      * Look for book output
8591      */
8592     if (cps == &first && bookRequested) {
8593         if (message[0] == '\t' || message[0] == ' ') {
8594             /* Part of the book output is here; append it */
8595             strcat(bookOutput, message);
8596             strcat(bookOutput, "  \n");
8597             return;
8598         } else if (bookOutput[0] != NULLCHAR) {
8599             /* All of book output has arrived; display it */
8600             char *p = bookOutput;
8601             while (*p != NULLCHAR) {
8602                 if (*p == '\t') *p = ' ';
8603                 p++;
8604             }
8605             DisplayInformation(bookOutput);
8606             bookRequested = FALSE;
8607             /* Fall through to parse the current output */
8608         }
8609     }
8610
8611     /*
8612      * Look for machine move.
8613      */
8614     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8615         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8616     {
8617         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8618             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8619             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8620             stalledEngine = cps;
8621             if(appData.ponderNextMove) { // bring opponent out of ponder
8622                 if(gameMode == TwoMachinesPlay) {
8623                     if(cps->other->pause)
8624                         PauseEngine(cps->other);
8625                     else
8626                         SendToProgram("easy\n", cps->other);
8627                 }
8628             }
8629             StopClocks();
8630             return;
8631         }
8632
8633         /* This method is only useful on engines that support ping */
8634         if (cps->lastPing != cps->lastPong) {
8635           if (gameMode == BeginningOfGame) {
8636             /* Extra move from before last new; ignore */
8637             if (appData.debugMode) {
8638                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8639             }
8640           } else {
8641             if (appData.debugMode) {
8642                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8643                         cps->which, gameMode);
8644             }
8645
8646             SendToProgram("undo\n", cps);
8647           }
8648           return;
8649         }
8650
8651         switch (gameMode) {
8652           case BeginningOfGame:
8653             /* Extra move from before last reset; ignore */
8654             if (appData.debugMode) {
8655                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8656             }
8657             return;
8658
8659           case EndOfGame:
8660           case IcsIdle:
8661           default:
8662             /* Extra move after we tried to stop.  The mode test is
8663                not a reliable way of detecting this problem, but it's
8664                the best we can do on engines that don't support ping.
8665             */
8666             if (appData.debugMode) {
8667                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8668                         cps->which, gameMode);
8669             }
8670             SendToProgram("undo\n", cps);
8671             return;
8672
8673           case MachinePlaysWhite:
8674           case IcsPlayingWhite:
8675             machineWhite = TRUE;
8676             break;
8677
8678           case MachinePlaysBlack:
8679           case IcsPlayingBlack:
8680             machineWhite = FALSE;
8681             break;
8682
8683           case TwoMachinesPlay:
8684             machineWhite = (cps->twoMachinesColor[0] == 'w');
8685             break;
8686         }
8687         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8688             if (appData.debugMode) {
8689                 fprintf(debugFP,
8690                         "Ignoring move out of turn by %s, gameMode %d"
8691                         ", forwardMost %d\n",
8692                         cps->which, gameMode, forwardMostMove);
8693             }
8694             return;
8695         }
8696
8697         if(cps->alphaRank) AlphaRank(machineMove, 4);
8698
8699         // [HGM] lion: (some very limited) support for Alien protocol
8700         killX = killY = kill2X = kill2Y = -1;
8701         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8702             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8703             return;
8704         }
8705         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8706             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8707             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8708         }
8709         if(firstLeg[0]) { // there was a previous leg;
8710             // only support case where same piece makes two step
8711             char buf[20], *p = machineMove+1, *q = buf+1, f;
8712             safeStrCpy(buf, machineMove, 20);
8713             while(isdigit(*q)) q++; // find start of to-square
8714             safeStrCpy(machineMove, firstLeg, 20);
8715             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8716             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8717             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8718             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8719             firstLeg[0] = NULLCHAR;
8720         }
8721
8722         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8723                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8724             /* Machine move could not be parsed; ignore it. */
8725           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8726                     machineMove, _(cps->which));
8727             DisplayMoveError(buf1);
8728             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8729                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8730             if (gameMode == TwoMachinesPlay) {
8731               GameEnds(machineWhite ? BlackWins : WhiteWins,
8732                        buf1, GE_XBOARD);
8733             }
8734             return;
8735         }
8736
8737         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8738         /* So we have to redo legality test with true e.p. status here,  */
8739         /* to make sure an illegal e.p. capture does not slip through,   */
8740         /* to cause a forfeit on a justified illegal-move complaint      */
8741         /* of the opponent.                                              */
8742         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8743            ChessMove moveType;
8744            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8745                              fromY, fromX, toY, toX, promoChar);
8746             if(moveType == IllegalMove) {
8747               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8748                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8749                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8750                            buf1, GE_XBOARD);
8751                 return;
8752            } else if(!appData.fischerCastling)
8753            /* [HGM] Kludge to handle engines that send FRC-style castling
8754               when they shouldn't (like TSCP-Gothic) */
8755            switch(moveType) {
8756              case WhiteASideCastleFR:
8757              case BlackASideCastleFR:
8758                toX+=2;
8759                currentMoveString[2]++;
8760                break;
8761              case WhiteHSideCastleFR:
8762              case BlackHSideCastleFR:
8763                toX--;
8764                currentMoveString[2]--;
8765                break;
8766              default: ; // nothing to do, but suppresses warning of pedantic compilers
8767            }
8768         }
8769         hintRequested = FALSE;
8770         lastHint[0] = NULLCHAR;
8771         bookRequested = FALSE;
8772         /* Program may be pondering now */
8773         cps->maybeThinking = TRUE;
8774         if (cps->sendTime == 2) cps->sendTime = 1;
8775         if (cps->offeredDraw) cps->offeredDraw--;
8776
8777         /* [AS] Save move info*/
8778         pvInfoList[ forwardMostMove ].score = programStats.score;
8779         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8780         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8781
8782         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8783
8784         /* Test suites abort the 'game' after one move */
8785         if(*appData.finger) {
8786            static FILE *f;
8787            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8788            if(!f) f = fopen(appData.finger, "w");
8789            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8790            else { DisplayFatalError("Bad output file", errno, 0); return; }
8791            free(fen);
8792            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8793         }
8794
8795         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8796         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8797             int count = 0;
8798
8799             while( count < adjudicateLossPlies ) {
8800                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8801
8802                 if( count & 1 ) {
8803                     score = -score; /* Flip score for winning side */
8804                 }
8805
8806                 if( score > appData.adjudicateLossThreshold ) {
8807                     break;
8808                 }
8809
8810                 count++;
8811             }
8812
8813             if( count >= adjudicateLossPlies ) {
8814                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8815
8816                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8817                     "Xboard adjudication",
8818                     GE_XBOARD );
8819
8820                 return;
8821             }
8822         }
8823
8824         if(Adjudicate(cps)) {
8825             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8826             return; // [HGM] adjudicate: for all automatic game ends
8827         }
8828
8829 #if ZIPPY
8830         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8831             first.initDone) {
8832           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8833                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8834                 SendToICS("draw ");
8835                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8836           }
8837           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8838           ics_user_moved = 1;
8839           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8840                 char buf[3*MSG_SIZ];
8841
8842                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8843                         programStats.score / 100.,
8844                         programStats.depth,
8845                         programStats.time / 100.,
8846                         (unsigned int)programStats.nodes,
8847                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8848                         programStats.movelist);
8849                 SendToICS(buf);
8850           }
8851         }
8852 #endif
8853
8854         /* [AS] Clear stats for next move */
8855         ClearProgramStats();
8856         thinkOutput[0] = NULLCHAR;
8857         hiddenThinkOutputState = 0;
8858
8859         bookHit = NULL;
8860         if (gameMode == TwoMachinesPlay) {
8861             /* [HGM] relaying draw offers moved to after reception of move */
8862             /* and interpreting offer as claim if it brings draw condition */
8863             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8864                 SendToProgram("draw\n", cps->other);
8865             }
8866             if (cps->other->sendTime) {
8867                 SendTimeRemaining(cps->other,
8868                                   cps->other->twoMachinesColor[0] == 'w');
8869             }
8870             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8871             if (firstMove && !bookHit) {
8872                 firstMove = FALSE;
8873                 if (cps->other->useColors) {
8874                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8875                 }
8876                 SendToProgram("go\n", cps->other);
8877             }
8878             cps->other->maybeThinking = TRUE;
8879         }
8880
8881         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8882
8883         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8884
8885         if (!pausing && appData.ringBellAfterMoves) {
8886             if(!roar) RingBell();
8887         }
8888
8889         /*
8890          * Reenable menu items that were disabled while
8891          * machine was thinking
8892          */
8893         if (gameMode != TwoMachinesPlay)
8894             SetUserThinkingEnables();
8895
8896         // [HGM] book: after book hit opponent has received move and is now in force mode
8897         // force the book reply into it, and then fake that it outputted this move by jumping
8898         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8899         if(bookHit) {
8900                 static char bookMove[MSG_SIZ]; // a bit generous?
8901
8902                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8903                 strcat(bookMove, bookHit);
8904                 message = bookMove;
8905                 cps = cps->other;
8906                 programStats.nodes = programStats.depth = programStats.time =
8907                 programStats.score = programStats.got_only_move = 0;
8908                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8909
8910                 if(cps->lastPing != cps->lastPong) {
8911                     savedMessage = message; // args for deferred call
8912                     savedState = cps;
8913                     ScheduleDelayedEvent(DeferredBookMove, 10);
8914                     return;
8915                 }
8916                 goto FakeBookMove;
8917         }
8918
8919         return;
8920     }
8921
8922     /* Set special modes for chess engines.  Later something general
8923      *  could be added here; for now there is just one kludge feature,
8924      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8925      *  when "xboard" is given as an interactive command.
8926      */
8927     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8928         cps->useSigint = FALSE;
8929         cps->useSigterm = FALSE;
8930     }
8931     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8932       ParseFeatures(message+8, cps);
8933       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8934     }
8935
8936     if (!strncmp(message, "setup ", 6) && 
8937         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8938           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8939                                         ) { // [HGM] allow first engine to define opening position
8940       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8941       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8942       *buf = NULLCHAR;
8943       if(sscanf(message, "setup (%s", buf) == 1) {
8944         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8945         ASSIGN(appData.pieceToCharTable, buf);
8946       }
8947       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8948       if(dummy >= 3) {
8949         while(message[s] && message[s++] != ' ');
8950         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8951            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8952             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8953             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8954           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8955           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8956           startedFromSetupPosition = FALSE;
8957         }
8958       }
8959       if(startedFromSetupPosition) return;
8960       ParseFEN(boards[0], &dummy, message+s, FALSE);
8961       DrawPosition(TRUE, boards[0]);
8962       CopyBoard(initialPosition, boards[0]);
8963       startedFromSetupPosition = TRUE;
8964       return;
8965     }
8966     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8967       ChessSquare piece = WhitePawn;
8968       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8969       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8970       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8971       piece += CharToPiece(ID) - WhitePawn;
8972       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8973       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8974       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8975       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8976       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8977       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8978                                                && gameInfo.variant != VariantGreat
8979                                                && gameInfo.variant != VariantFairy    ) return;
8980       if(piece < EmptySquare) {
8981         pieceDefs = TRUE;
8982         ASSIGN(pieceDesc[piece], buf1);
8983         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8984       }
8985       return;
8986     }
8987     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8988      * want this, I was asked to put it in, and obliged.
8989      */
8990     if (!strncmp(message, "setboard ", 9)) {
8991         Board initial_position;
8992
8993         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8994
8995         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8996             DisplayError(_("Bad FEN received from engine"), 0);
8997             return ;
8998         } else {
8999            Reset(TRUE, FALSE);
9000            CopyBoard(boards[0], initial_position);
9001            initialRulePlies = FENrulePlies;
9002            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9003            else gameMode = MachinePlaysBlack;
9004            DrawPosition(FALSE, boards[currentMove]);
9005         }
9006         return;
9007     }
9008
9009     /*
9010      * Look for communication commands
9011      */
9012     if (!strncmp(message, "telluser ", 9)) {
9013         if(message[9] == '\\' && message[10] == '\\')
9014             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9015         PlayTellSound();
9016         DisplayNote(message + 9);
9017         return;
9018     }
9019     if (!strncmp(message, "tellusererror ", 14)) {
9020         cps->userError = 1;
9021         if(message[14] == '\\' && message[15] == '\\')
9022             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9023         PlayTellSound();
9024         DisplayError(message + 14, 0);
9025         return;
9026     }
9027     if (!strncmp(message, "tellopponent ", 13)) {
9028       if (appData.icsActive) {
9029         if (loggedOn) {
9030           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9031           SendToICS(buf1);
9032         }
9033       } else {
9034         DisplayNote(message + 13);
9035       }
9036       return;
9037     }
9038     if (!strncmp(message, "tellothers ", 11)) {
9039       if (appData.icsActive) {
9040         if (loggedOn) {
9041           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9042           SendToICS(buf1);
9043         }
9044       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9045       return;
9046     }
9047     if (!strncmp(message, "tellall ", 8)) {
9048       if (appData.icsActive) {
9049         if (loggedOn) {
9050           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9051           SendToICS(buf1);
9052         }
9053       } else {
9054         DisplayNote(message + 8);
9055       }
9056       return;
9057     }
9058     if (strncmp(message, "warning", 7) == 0) {
9059         /* Undocumented feature, use tellusererror in new code */
9060         DisplayError(message, 0);
9061         return;
9062     }
9063     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9064         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9065         strcat(realname, " query");
9066         AskQuestion(realname, buf2, buf1, cps->pr);
9067         return;
9068     }
9069     /* Commands from the engine directly to ICS.  We don't allow these to be
9070      *  sent until we are logged on. Crafty kibitzes have been known to
9071      *  interfere with the login process.
9072      */
9073     if (loggedOn) {
9074         if (!strncmp(message, "tellics ", 8)) {
9075             SendToICS(message + 8);
9076             SendToICS("\n");
9077             return;
9078         }
9079         if (!strncmp(message, "tellicsnoalias ", 15)) {
9080             SendToICS(ics_prefix);
9081             SendToICS(message + 15);
9082             SendToICS("\n");
9083             return;
9084         }
9085         /* The following are for backward compatibility only */
9086         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9087             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9088             SendToICS(ics_prefix);
9089             SendToICS(message);
9090             SendToICS("\n");
9091             return;
9092         }
9093     }
9094     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9095         if(initPing == cps->lastPong) {
9096             if(gameInfo.variant == VariantUnknown) {
9097                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9098                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9099                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9100             }
9101             initPing = -1;
9102         }
9103         return;
9104     }
9105     if(!strncmp(message, "highlight ", 10)) {
9106         if(appData.testLegality && !*engineVariant && appData.markers) return;
9107         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9108         return;
9109     }
9110     if(!strncmp(message, "click ", 6)) {
9111         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9112         if(appData.testLegality || !appData.oneClick) return;
9113         sscanf(message+6, "%c%d%c", &f, &y, &c);
9114         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9115         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9116         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9117         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9118         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9119         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9120             LeftClick(Release, lastLeftX, lastLeftY);
9121         controlKey  = (c == ',');
9122         LeftClick(Press, x, y);
9123         LeftClick(Release, x, y);
9124         first.highlight = f;
9125         return;
9126     }
9127     /*
9128      * If the move is illegal, cancel it and redraw the board.
9129      * Also deal with other error cases.  Matching is rather loose
9130      * here to accommodate engines written before the spec.
9131      */
9132     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9133         strncmp(message, "Error", 5) == 0) {
9134         if (StrStr(message, "name") ||
9135             StrStr(message, "rating") || StrStr(message, "?") ||
9136             StrStr(message, "result") || StrStr(message, "board") ||
9137             StrStr(message, "bk") || StrStr(message, "computer") ||
9138             StrStr(message, "variant") || StrStr(message, "hint") ||
9139             StrStr(message, "random") || StrStr(message, "depth") ||
9140             StrStr(message, "accepted")) {
9141             return;
9142         }
9143         if (StrStr(message, "protover")) {
9144           /* Program is responding to input, so it's apparently done
9145              initializing, and this error message indicates it is
9146              protocol version 1.  So we don't need to wait any longer
9147              for it to initialize and send feature commands. */
9148           FeatureDone(cps, 1);
9149           cps->protocolVersion = 1;
9150           return;
9151         }
9152         cps->maybeThinking = FALSE;
9153
9154         if (StrStr(message, "draw")) {
9155             /* Program doesn't have "draw" command */
9156             cps->sendDrawOffers = 0;
9157             return;
9158         }
9159         if (cps->sendTime != 1 &&
9160             (StrStr(message, "time") || StrStr(message, "otim"))) {
9161           /* Program apparently doesn't have "time" or "otim" command */
9162           cps->sendTime = 0;
9163           return;
9164         }
9165         if (StrStr(message, "analyze")) {
9166             cps->analysisSupport = FALSE;
9167             cps->analyzing = FALSE;
9168 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9169             EditGameEvent(); // [HGM] try to preserve loaded game
9170             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9171             DisplayError(buf2, 0);
9172             return;
9173         }
9174         if (StrStr(message, "(no matching move)st")) {
9175           /* Special kludge for GNU Chess 4 only */
9176           cps->stKludge = TRUE;
9177           SendTimeControl(cps, movesPerSession, timeControl,
9178                           timeIncrement, appData.searchDepth,
9179                           searchTime);
9180           return;
9181         }
9182         if (StrStr(message, "(no matching move)sd")) {
9183           /* Special kludge for GNU Chess 4 only */
9184           cps->sdKludge = TRUE;
9185           SendTimeControl(cps, movesPerSession, timeControl,
9186                           timeIncrement, appData.searchDepth,
9187                           searchTime);
9188           return;
9189         }
9190         if (!StrStr(message, "llegal")) {
9191             return;
9192         }
9193         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9194             gameMode == IcsIdle) return;
9195         if (forwardMostMove <= backwardMostMove) return;
9196         if (pausing) PauseEvent();
9197       if(appData.forceIllegal) {
9198             // [HGM] illegal: machine refused move; force position after move into it
9199           SendToProgram("force\n", cps);
9200           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9201                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9202                 // when black is to move, while there might be nothing on a2 or black
9203                 // might already have the move. So send the board as if white has the move.
9204                 // But first we must change the stm of the engine, as it refused the last move
9205                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9206                 if(WhiteOnMove(forwardMostMove)) {
9207                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9208                     SendBoard(cps, forwardMostMove); // kludgeless board
9209                 } else {
9210                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9211                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9212                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9213                 }
9214           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9215             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9216                  gameMode == TwoMachinesPlay)
9217               SendToProgram("go\n", cps);
9218             return;
9219       } else
9220         if (gameMode == PlayFromGameFile) {
9221             /* Stop reading this game file */
9222             gameMode = EditGame;
9223             ModeHighlight();
9224         }
9225         /* [HGM] illegal-move claim should forfeit game when Xboard */
9226         /* only passes fully legal moves                            */
9227         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9228             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9229                                 "False illegal-move claim", GE_XBOARD );
9230             return; // do not take back move we tested as valid
9231         }
9232         currentMove = forwardMostMove-1;
9233         DisplayMove(currentMove-1); /* before DisplayMoveError */
9234         SwitchClocks(forwardMostMove-1); // [HGM] race
9235         DisplayBothClocks();
9236         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9237                 parseList[currentMove], _(cps->which));
9238         DisplayMoveError(buf1);
9239         DrawPosition(FALSE, boards[currentMove]);
9240
9241         SetUserThinkingEnables();
9242         return;
9243     }
9244     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9245         /* Program has a broken "time" command that
9246            outputs a string not ending in newline.
9247            Don't use it. */
9248         cps->sendTime = 0;
9249     }
9250     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9251         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9252             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9253     }
9254
9255     /*
9256      * If chess program startup fails, exit with an error message.
9257      * Attempts to recover here are futile. [HGM] Well, we try anyway
9258      */
9259     if ((StrStr(message, "unknown host") != NULL)
9260         || (StrStr(message, "No remote directory") != NULL)
9261         || (StrStr(message, "not found") != NULL)
9262         || (StrStr(message, "No such file") != NULL)
9263         || (StrStr(message, "can't alloc") != NULL)
9264         || (StrStr(message, "Permission denied") != NULL)) {
9265
9266         cps->maybeThinking = FALSE;
9267         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9268                 _(cps->which), cps->program, cps->host, message);
9269         RemoveInputSource(cps->isr);
9270         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9271             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9272             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9273         }
9274         return;
9275     }
9276
9277     /*
9278      * Look for hint output
9279      */
9280     if (sscanf(message, "Hint: %s", buf1) == 1) {
9281         if (cps == &first && hintRequested) {
9282             hintRequested = FALSE;
9283             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9284                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9285                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9286                                     PosFlags(forwardMostMove),
9287                                     fromY, fromX, toY, toX, promoChar, buf1);
9288                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9289                 DisplayInformation(buf2);
9290             } else {
9291                 /* Hint move could not be parsed!? */
9292               snprintf(buf2, sizeof(buf2),
9293                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9294                         buf1, _(cps->which));
9295                 DisplayError(buf2, 0);
9296             }
9297         } else {
9298           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9299         }
9300         return;
9301     }
9302
9303     /*
9304      * Ignore other messages if game is not in progress
9305      */
9306     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9307         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9308
9309     /*
9310      * look for win, lose, draw, or draw offer
9311      */
9312     if (strncmp(message, "1-0", 3) == 0) {
9313         char *p, *q, *r = "";
9314         p = strchr(message, '{');
9315         if (p) {
9316             q = strchr(p, '}');
9317             if (q) {
9318                 *q = NULLCHAR;
9319                 r = p + 1;
9320             }
9321         }
9322         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9323         return;
9324     } else if (strncmp(message, "0-1", 3) == 0) {
9325         char *p, *q, *r = "";
9326         p = strchr(message, '{');
9327         if (p) {
9328             q = strchr(p, '}');
9329             if (q) {
9330                 *q = NULLCHAR;
9331                 r = p + 1;
9332             }
9333         }
9334         /* Kludge for Arasan 4.1 bug */
9335         if (strcmp(r, "Black resigns") == 0) {
9336             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9337             return;
9338         }
9339         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9340         return;
9341     } else if (strncmp(message, "1/2", 3) == 0) {
9342         char *p, *q, *r = "";
9343         p = strchr(message, '{');
9344         if (p) {
9345             q = strchr(p, '}');
9346             if (q) {
9347                 *q = NULLCHAR;
9348                 r = p + 1;
9349             }
9350         }
9351
9352         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9353         return;
9354
9355     } else if (strncmp(message, "White resign", 12) == 0) {
9356         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9357         return;
9358     } else if (strncmp(message, "Black resign", 12) == 0) {
9359         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9360         return;
9361     } else if (strncmp(message, "White matches", 13) == 0 ||
9362                strncmp(message, "Black matches", 13) == 0   ) {
9363         /* [HGM] ignore GNUShogi noises */
9364         return;
9365     } else if (strncmp(message, "White", 5) == 0 &&
9366                message[5] != '(' &&
9367                StrStr(message, "Black") == NULL) {
9368         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9369         return;
9370     } else if (strncmp(message, "Black", 5) == 0 &&
9371                message[5] != '(') {
9372         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9373         return;
9374     } else if (strcmp(message, "resign") == 0 ||
9375                strcmp(message, "computer resigns") == 0) {
9376         switch (gameMode) {
9377           case MachinePlaysBlack:
9378           case IcsPlayingBlack:
9379             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9380             break;
9381           case MachinePlaysWhite:
9382           case IcsPlayingWhite:
9383             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9384             break;
9385           case TwoMachinesPlay:
9386             if (cps->twoMachinesColor[0] == 'w')
9387               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9388             else
9389               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9390             break;
9391           default:
9392             /* can't happen */
9393             break;
9394         }
9395         return;
9396     } else if (strncmp(message, "opponent mates", 14) == 0) {
9397         switch (gameMode) {
9398           case MachinePlaysBlack:
9399           case IcsPlayingBlack:
9400             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9401             break;
9402           case MachinePlaysWhite:
9403           case IcsPlayingWhite:
9404             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9405             break;
9406           case TwoMachinesPlay:
9407             if (cps->twoMachinesColor[0] == 'w')
9408               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9409             else
9410               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9411             break;
9412           default:
9413             /* can't happen */
9414             break;
9415         }
9416         return;
9417     } else if (strncmp(message, "computer mates", 14) == 0) {
9418         switch (gameMode) {
9419           case MachinePlaysBlack:
9420           case IcsPlayingBlack:
9421             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9422             break;
9423           case MachinePlaysWhite:
9424           case IcsPlayingWhite:
9425             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9426             break;
9427           case TwoMachinesPlay:
9428             if (cps->twoMachinesColor[0] == 'w')
9429               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9430             else
9431               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9432             break;
9433           default:
9434             /* can't happen */
9435             break;
9436         }
9437         return;
9438     } else if (strncmp(message, "checkmate", 9) == 0) {
9439         if (WhiteOnMove(forwardMostMove)) {
9440             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9441         } else {
9442             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9443         }
9444         return;
9445     } else if (strstr(message, "Draw") != NULL ||
9446                strstr(message, "game is a draw") != NULL) {
9447         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9448         return;
9449     } else if (strstr(message, "offer") != NULL &&
9450                strstr(message, "draw") != NULL) {
9451 #if ZIPPY
9452         if (appData.zippyPlay && first.initDone) {
9453             /* Relay offer to ICS */
9454             SendToICS(ics_prefix);
9455             SendToICS("draw\n");
9456         }
9457 #endif
9458         cps->offeredDraw = 2; /* valid until this engine moves twice */
9459         if (gameMode == TwoMachinesPlay) {
9460             if (cps->other->offeredDraw) {
9461                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9462             /* [HGM] in two-machine mode we delay relaying draw offer      */
9463             /* until after we also have move, to see if it is really claim */
9464             }
9465         } else if (gameMode == MachinePlaysWhite ||
9466                    gameMode == MachinePlaysBlack) {
9467           if (userOfferedDraw) {
9468             DisplayInformation(_("Machine accepts your draw offer"));
9469             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9470           } else {
9471             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9472           }
9473         }
9474     }
9475
9476
9477     /*
9478      * Look for thinking output
9479      */
9480     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9481           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9482                                 ) {
9483         int plylev, mvleft, mvtot, curscore, time;
9484         char mvname[MOVE_LEN];
9485         u64 nodes; // [DM]
9486         char plyext;
9487         int ignore = FALSE;
9488         int prefixHint = FALSE;
9489         mvname[0] = NULLCHAR;
9490
9491         switch (gameMode) {
9492           case MachinePlaysBlack:
9493           case IcsPlayingBlack:
9494             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9495             break;
9496           case MachinePlaysWhite:
9497           case IcsPlayingWhite:
9498             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9499             break;
9500           case AnalyzeMode:
9501           case AnalyzeFile:
9502             break;
9503           case IcsObserving: /* [DM] icsEngineAnalyze */
9504             if (!appData.icsEngineAnalyze) ignore = TRUE;
9505             break;
9506           case TwoMachinesPlay:
9507             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9508                 ignore = TRUE;
9509             }
9510             break;
9511           default:
9512             ignore = TRUE;
9513             break;
9514         }
9515
9516         if (!ignore) {
9517             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9518             buf1[0] = NULLCHAR;
9519             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9520                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9521
9522                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9523                     nodes += u64Const(0x100000000);
9524
9525                 if (plyext != ' ' && plyext != '\t') {
9526                     time *= 100;
9527                 }
9528
9529                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9530                 if( cps->scoreIsAbsolute &&
9531                     ( gameMode == MachinePlaysBlack ||
9532                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9533                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9534                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9535                      !WhiteOnMove(currentMove)
9536                     ) )
9537                 {
9538                     curscore = -curscore;
9539                 }
9540
9541                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9542
9543                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9544                         char buf[MSG_SIZ];
9545                         FILE *f;
9546                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9547                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9548                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9549                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9550                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9551                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9552                                 fclose(f);
9553                         }
9554                         else
9555                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9556                           DisplayError(_("failed writing PV"), 0);
9557                 }
9558
9559                 tempStats.depth = plylev;
9560                 tempStats.nodes = nodes;
9561                 tempStats.time = time;
9562                 tempStats.score = curscore;
9563                 tempStats.got_only_move = 0;
9564
9565                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9566                         int ticklen;
9567
9568                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9569                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9570                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9571                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9572                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9573                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9574                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9575                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9576                 }
9577
9578                 /* Buffer overflow protection */
9579                 if (pv[0] != NULLCHAR) {
9580                     if (strlen(pv) >= sizeof(tempStats.movelist)
9581                         && appData.debugMode) {
9582                         fprintf(debugFP,
9583                                 "PV is too long; using the first %u bytes.\n",
9584                                 (unsigned) sizeof(tempStats.movelist) - 1);
9585                     }
9586
9587                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9588                 } else {
9589                     sprintf(tempStats.movelist, " no PV\n");
9590                 }
9591
9592                 if (tempStats.seen_stat) {
9593                     tempStats.ok_to_send = 1;
9594                 }
9595
9596                 if (strchr(tempStats.movelist, '(') != NULL) {
9597                     tempStats.line_is_book = 1;
9598                     tempStats.nr_moves = 0;
9599                     tempStats.moves_left = 0;
9600                 } else {
9601                     tempStats.line_is_book = 0;
9602                 }
9603
9604                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9605                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9606
9607                 SendProgramStatsToFrontend( cps, &tempStats );
9608
9609                 /*
9610                     [AS] Protect the thinkOutput buffer from overflow... this
9611                     is only useful if buf1 hasn't overflowed first!
9612                 */
9613                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9614                          plylev,
9615                          (gameMode == TwoMachinesPlay ?
9616                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9617                          ((double) curscore) / 100.0,
9618                          prefixHint ? lastHint : "",
9619                          prefixHint ? " " : "" );
9620
9621                 if( buf1[0] != NULLCHAR ) {
9622                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9623
9624                     if( strlen(pv) > max_len ) {
9625                         if( appData.debugMode) {
9626                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9627                         }
9628                         pv[max_len+1] = '\0';
9629                     }
9630
9631                     strcat( thinkOutput, pv);
9632                 }
9633
9634                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9635                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9636                     DisplayMove(currentMove - 1);
9637                 }
9638                 return;
9639
9640             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9641                 /* crafty (9.25+) says "(only move) <move>"
9642                  * if there is only 1 legal move
9643                  */
9644                 sscanf(p, "(only move) %s", buf1);
9645                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9646                 sprintf(programStats.movelist, "%s (only move)", buf1);
9647                 programStats.depth = 1;
9648                 programStats.nr_moves = 1;
9649                 programStats.moves_left = 1;
9650                 programStats.nodes = 1;
9651                 programStats.time = 1;
9652                 programStats.got_only_move = 1;
9653
9654                 /* Not really, but we also use this member to
9655                    mean "line isn't going to change" (Crafty
9656                    isn't searching, so stats won't change) */
9657                 programStats.line_is_book = 1;
9658
9659                 SendProgramStatsToFrontend( cps, &programStats );
9660
9661                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9662                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9663                     DisplayMove(currentMove - 1);
9664                 }
9665                 return;
9666             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9667                               &time, &nodes, &plylev, &mvleft,
9668                               &mvtot, mvname) >= 5) {
9669                 /* The stat01: line is from Crafty (9.29+) in response
9670                    to the "." command */
9671                 programStats.seen_stat = 1;
9672                 cps->maybeThinking = TRUE;
9673
9674                 if (programStats.got_only_move || !appData.periodicUpdates)
9675                   return;
9676
9677                 programStats.depth = plylev;
9678                 programStats.time = time;
9679                 programStats.nodes = nodes;
9680                 programStats.moves_left = mvleft;
9681                 programStats.nr_moves = mvtot;
9682                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9683                 programStats.ok_to_send = 1;
9684                 programStats.movelist[0] = '\0';
9685
9686                 SendProgramStatsToFrontend( cps, &programStats );
9687
9688                 return;
9689
9690             } else if (strncmp(message,"++",2) == 0) {
9691                 /* Crafty 9.29+ outputs this */
9692                 programStats.got_fail = 2;
9693                 return;
9694
9695             } else if (strncmp(message,"--",2) == 0) {
9696                 /* Crafty 9.29+ outputs this */
9697                 programStats.got_fail = 1;
9698                 return;
9699
9700             } else if (thinkOutput[0] != NULLCHAR &&
9701                        strncmp(message, "    ", 4) == 0) {
9702                 unsigned message_len;
9703
9704                 p = message;
9705                 while (*p && *p == ' ') p++;
9706
9707                 message_len = strlen( p );
9708
9709                 /* [AS] Avoid buffer overflow */
9710                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9711                     strcat(thinkOutput, " ");
9712                     strcat(thinkOutput, p);
9713                 }
9714
9715                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9716                     strcat(programStats.movelist, " ");
9717                     strcat(programStats.movelist, p);
9718                 }
9719
9720                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9721                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9722                     DisplayMove(currentMove - 1);
9723                 }
9724                 return;
9725             }
9726         }
9727         else {
9728             buf1[0] = NULLCHAR;
9729
9730             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9731                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9732             {
9733                 ChessProgramStats cpstats;
9734
9735                 if (plyext != ' ' && plyext != '\t') {
9736                     time *= 100;
9737                 }
9738
9739                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9740                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9741                     curscore = -curscore;
9742                 }
9743
9744                 cpstats.depth = plylev;
9745                 cpstats.nodes = nodes;
9746                 cpstats.time = time;
9747                 cpstats.score = curscore;
9748                 cpstats.got_only_move = 0;
9749                 cpstats.movelist[0] = '\0';
9750
9751                 if (buf1[0] != NULLCHAR) {
9752                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9753                 }
9754
9755                 cpstats.ok_to_send = 0;
9756                 cpstats.line_is_book = 0;
9757                 cpstats.nr_moves = 0;
9758                 cpstats.moves_left = 0;
9759
9760                 SendProgramStatsToFrontend( cps, &cpstats );
9761             }
9762         }
9763     }
9764 }
9765
9766
9767 /* Parse a game score from the character string "game", and
9768    record it as the history of the current game.  The game
9769    score is NOT assumed to start from the standard position.
9770    The display is not updated in any way.
9771    */
9772 void
9773 ParseGameHistory (char *game)
9774 {
9775     ChessMove moveType;
9776     int fromX, fromY, toX, toY, boardIndex;
9777     char promoChar;
9778     char *p, *q;
9779     char buf[MSG_SIZ];
9780
9781     if (appData.debugMode)
9782       fprintf(debugFP, "Parsing game history: %s\n", game);
9783
9784     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9785     gameInfo.site = StrSave(appData.icsHost);
9786     gameInfo.date = PGNDate();
9787     gameInfo.round = StrSave("-");
9788
9789     /* Parse out names of players */
9790     while (*game == ' ') game++;
9791     p = buf;
9792     while (*game != ' ') *p++ = *game++;
9793     *p = NULLCHAR;
9794     gameInfo.white = StrSave(buf);
9795     while (*game == ' ') game++;
9796     p = buf;
9797     while (*game != ' ' && *game != '\n') *p++ = *game++;
9798     *p = NULLCHAR;
9799     gameInfo.black = StrSave(buf);
9800
9801     /* Parse moves */
9802     boardIndex = blackPlaysFirst ? 1 : 0;
9803     yynewstr(game);
9804     for (;;) {
9805         yyboardindex = boardIndex;
9806         moveType = (ChessMove) Myylex();
9807         switch (moveType) {
9808           case IllegalMove:             /* maybe suicide chess, etc. */
9809   if (appData.debugMode) {
9810     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9811     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9812     setbuf(debugFP, NULL);
9813   }
9814           case WhitePromotion:
9815           case BlackPromotion:
9816           case WhiteNonPromotion:
9817           case BlackNonPromotion:
9818           case NormalMove:
9819           case FirstLeg:
9820           case WhiteCapturesEnPassant:
9821           case BlackCapturesEnPassant:
9822           case WhiteKingSideCastle:
9823           case WhiteQueenSideCastle:
9824           case BlackKingSideCastle:
9825           case BlackQueenSideCastle:
9826           case WhiteKingSideCastleWild:
9827           case WhiteQueenSideCastleWild:
9828           case BlackKingSideCastleWild:
9829           case BlackQueenSideCastleWild:
9830           /* PUSH Fabien */
9831           case WhiteHSideCastleFR:
9832           case WhiteASideCastleFR:
9833           case BlackHSideCastleFR:
9834           case BlackASideCastleFR:
9835           /* POP Fabien */
9836             fromX = currentMoveString[0] - AAA;
9837             fromY = currentMoveString[1] - ONE;
9838             toX = currentMoveString[2] - AAA;
9839             toY = currentMoveString[3] - ONE;
9840             promoChar = currentMoveString[4];
9841             break;
9842           case WhiteDrop:
9843           case BlackDrop:
9844             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9845             fromX = moveType == WhiteDrop ?
9846               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9847             (int) CharToPiece(ToLower(currentMoveString[0]));
9848             fromY = DROP_RANK;
9849             toX = currentMoveString[2] - AAA;
9850             toY = currentMoveString[3] - ONE;
9851             promoChar = NULLCHAR;
9852             break;
9853           case AmbiguousMove:
9854             /* bug? */
9855             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9856   if (appData.debugMode) {
9857     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9858     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9859     setbuf(debugFP, NULL);
9860   }
9861             DisplayError(buf, 0);
9862             return;
9863           case ImpossibleMove:
9864             /* bug? */
9865             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9866   if (appData.debugMode) {
9867     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9868     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9869     setbuf(debugFP, NULL);
9870   }
9871             DisplayError(buf, 0);
9872             return;
9873           case EndOfFile:
9874             if (boardIndex < backwardMostMove) {
9875                 /* Oops, gap.  How did that happen? */
9876                 DisplayError(_("Gap in move list"), 0);
9877                 return;
9878             }
9879             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9880             if (boardIndex > forwardMostMove) {
9881                 forwardMostMove = boardIndex;
9882             }
9883             return;
9884           case ElapsedTime:
9885             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9886                 strcat(parseList[boardIndex-1], " ");
9887                 strcat(parseList[boardIndex-1], yy_text);
9888             }
9889             continue;
9890           case Comment:
9891           case PGNTag:
9892           case NAG:
9893           default:
9894             /* ignore */
9895             continue;
9896           case WhiteWins:
9897           case BlackWins:
9898           case GameIsDrawn:
9899           case GameUnfinished:
9900             if (gameMode == IcsExamining) {
9901                 if (boardIndex < backwardMostMove) {
9902                     /* Oops, gap.  How did that happen? */
9903                     return;
9904                 }
9905                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9906                 return;
9907             }
9908             gameInfo.result = moveType;
9909             p = strchr(yy_text, '{');
9910             if (p == NULL) p = strchr(yy_text, '(');
9911             if (p == NULL) {
9912                 p = yy_text;
9913                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9914             } else {
9915                 q = strchr(p, *p == '{' ? '}' : ')');
9916                 if (q != NULL) *q = NULLCHAR;
9917                 p++;
9918             }
9919             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9920             gameInfo.resultDetails = StrSave(p);
9921             continue;
9922         }
9923         if (boardIndex >= forwardMostMove &&
9924             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9925             backwardMostMove = blackPlaysFirst ? 1 : 0;
9926             return;
9927         }
9928         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9929                                  fromY, fromX, toY, toX, promoChar,
9930                                  parseList[boardIndex]);
9931         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9932         /* currentMoveString is set as a side-effect of yylex */
9933         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9934         strcat(moveList[boardIndex], "\n");
9935         boardIndex++;
9936         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9937         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9938           case MT_NONE:
9939           case MT_STALEMATE:
9940           default:
9941             break;
9942           case MT_CHECK:
9943             if(!IS_SHOGI(gameInfo.variant))
9944                 strcat(parseList[boardIndex - 1], "+");
9945             break;
9946           case MT_CHECKMATE:
9947           case MT_STAINMATE:
9948             strcat(parseList[boardIndex - 1], "#");
9949             break;
9950         }
9951     }
9952 }
9953
9954
9955 /* Apply a move to the given board  */
9956 void
9957 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9958 {
9959   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9960   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9961
9962     /* [HGM] compute & store e.p. status and castling rights for new position */
9963     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9964
9965       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9966       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9967       board[EP_STATUS] = EP_NONE;
9968       board[EP_FILE] = board[EP_RANK] = 100;
9969
9970   if (fromY == DROP_RANK) {
9971         /* must be first */
9972         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9973             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9974             return;
9975         }
9976         piece = board[toY][toX] = (ChessSquare) fromX;
9977   } else {
9978 //      ChessSquare victim;
9979       int i;
9980
9981       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
9982 //           victim = board[killY][killX],
9983            killed = board[killY][killX],
9984            board[killY][killX] = EmptySquare,
9985            board[EP_STATUS] = EP_CAPTURE;
9986            if( kill2X >= 0 && kill2Y >= 0)
9987              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
9988       }
9989
9990       if( board[toY][toX] != EmptySquare ) {
9991            board[EP_STATUS] = EP_CAPTURE;
9992            if( (fromX != toX || fromY != toY) && // not igui!
9993                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9994                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9995                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9996            }
9997       }
9998
9999       pawn = board[fromY][fromX];
10000       if( pawn == WhiteLance || pawn == BlackLance ) {
10001            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10002                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10003                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10004            }
10005       }
10006       if( pawn == WhitePawn ) {
10007            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10008                board[EP_STATUS] = EP_PAWN_MOVE;
10009            if( toY-fromY>=2) {
10010                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10011                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10012                         gameInfo.variant != VariantBerolina || toX < fromX)
10013                       board[EP_STATUS] = toX | berolina;
10014                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10015                         gameInfo.variant != VariantBerolina || toX > fromX)
10016                       board[EP_STATUS] = toX;
10017            }
10018       } else
10019       if( pawn == BlackPawn ) {
10020            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10021                board[EP_STATUS] = EP_PAWN_MOVE;
10022            if( toY-fromY<= -2) {
10023                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10024                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10025                         gameInfo.variant != VariantBerolina || toX < fromX)
10026                       board[EP_STATUS] = toX | berolina;
10027                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10028                         gameInfo.variant != VariantBerolina || toX > fromX)
10029                       board[EP_STATUS] = toX;
10030            }
10031        }
10032
10033        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10034        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10035        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10036        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10037
10038        for(i=0; i<nrCastlingRights; i++) {
10039            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10040               board[CASTLING][i] == toX   && castlingRank[i] == toY
10041              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10042        }
10043
10044        if(gameInfo.variant == VariantSChess) { // update virginity
10045            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10046            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10047            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10048            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10049        }
10050
10051      if (fromX == toX && fromY == toY) return;
10052
10053      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10054      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10055      if(gameInfo.variant == VariantKnightmate)
10056          king += (int) WhiteUnicorn - (int) WhiteKing;
10057
10058     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10059        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10060         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10061         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10062         board[EP_STATUS] = EP_NONE; // capture was fake!
10063     } else
10064     /* Code added by Tord: */
10065     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10066     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10067         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10068       board[EP_STATUS] = EP_NONE; // capture was fake!
10069       board[fromY][fromX] = EmptySquare;
10070       board[toY][toX] = EmptySquare;
10071       if((toX > fromX) != (piece == WhiteRook)) {
10072         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10073       } else {
10074         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10075       }
10076     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10077                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10078       board[EP_STATUS] = EP_NONE;
10079       board[fromY][fromX] = EmptySquare;
10080       board[toY][toX] = EmptySquare;
10081       if((toX > fromX) != (piece == BlackRook)) {
10082         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10083       } else {
10084         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10085       }
10086     /* End of code added by Tord */
10087
10088     } else if (board[fromY][fromX] == king
10089         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10090         && toY == fromY && toX > fromX+1) {
10091         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10092         board[fromY][toX-1] = board[fromY][rookX];
10093         board[fromY][rookX] = EmptySquare;
10094         board[fromY][fromX] = EmptySquare;
10095         board[toY][toX] = king;
10096     } else if (board[fromY][fromX] == king
10097         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10098                && toY == fromY && toX < fromX-1) {
10099         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10100         board[fromY][toX+1] = board[fromY][rookX];
10101         board[fromY][rookX] = EmptySquare;
10102         board[fromY][fromX] = EmptySquare;
10103         board[toY][toX] = king;
10104     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10105                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10106                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10107                ) {
10108         /* white pawn promotion */
10109         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10110         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10111             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10112         board[fromY][fromX] = EmptySquare;
10113     } else if ((fromY >= BOARD_HEIGHT>>1)
10114                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10115                && (toX != fromX)
10116                && gameInfo.variant != VariantXiangqi
10117                && gameInfo.variant != VariantBerolina
10118                && (pawn == WhitePawn)
10119                && (board[toY][toX] == EmptySquare)) {
10120         board[fromY][fromX] = EmptySquare;
10121         board[toY][toX] = piece;
10122         if(toY == epRank - 128 + 1)
10123             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10124         else
10125             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10126     } else if ((fromY == BOARD_HEIGHT-4)
10127                && (toX == fromX)
10128                && gameInfo.variant == VariantBerolina
10129                && (board[fromY][fromX] == WhitePawn)
10130                && (board[toY][toX] == EmptySquare)) {
10131         board[fromY][fromX] = EmptySquare;
10132         board[toY][toX] = WhitePawn;
10133         if(oldEP & EP_BEROLIN_A) {
10134                 captured = board[fromY][fromX-1];
10135                 board[fromY][fromX-1] = EmptySquare;
10136         }else{  captured = board[fromY][fromX+1];
10137                 board[fromY][fromX+1] = EmptySquare;
10138         }
10139     } else if (board[fromY][fromX] == king
10140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10141                && toY == fromY && toX > fromX+1) {
10142         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10143         board[fromY][toX-1] = board[fromY][rookX];
10144         board[fromY][rookX] = EmptySquare;
10145         board[fromY][fromX] = EmptySquare;
10146         board[toY][toX] = king;
10147     } else if (board[fromY][fromX] == king
10148         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10149                && toY == fromY && toX < fromX-1) {
10150         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10151         board[fromY][toX+1] = board[fromY][rookX];
10152         board[fromY][rookX] = EmptySquare;
10153         board[fromY][fromX] = EmptySquare;
10154         board[toY][toX] = king;
10155     } else if (fromY == 7 && fromX == 3
10156                && board[fromY][fromX] == BlackKing
10157                && toY == 7 && toX == 5) {
10158         board[fromY][fromX] = EmptySquare;
10159         board[toY][toX] = BlackKing;
10160         board[fromY][7] = EmptySquare;
10161         board[toY][4] = BlackRook;
10162     } else if (fromY == 7 && fromX == 3
10163                && board[fromY][fromX] == BlackKing
10164                && toY == 7 && toX == 1) {
10165         board[fromY][fromX] = EmptySquare;
10166         board[toY][toX] = BlackKing;
10167         board[fromY][0] = EmptySquare;
10168         board[toY][2] = BlackRook;
10169     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10170                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10171                && toY < promoRank && promoChar
10172                ) {
10173         /* black pawn promotion */
10174         board[toY][toX] = CharToPiece(ToLower(promoChar));
10175         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10176             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10177         board[fromY][fromX] = EmptySquare;
10178     } else if ((fromY < BOARD_HEIGHT>>1)
10179                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10180                && (toX != fromX)
10181                && gameInfo.variant != VariantXiangqi
10182                && gameInfo.variant != VariantBerolina
10183                && (pawn == BlackPawn)
10184                && (board[toY][toX] == EmptySquare)) {
10185         board[fromY][fromX] = EmptySquare;
10186         board[toY][toX] = piece;
10187         if(toY == epRank - 128 - 1)
10188             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10189         else
10190             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10191     } else if ((fromY == 3)
10192                && (toX == fromX)
10193                && gameInfo.variant == VariantBerolina
10194                && (board[fromY][fromX] == BlackPawn)
10195                && (board[toY][toX] == EmptySquare)) {
10196         board[fromY][fromX] = EmptySquare;
10197         board[toY][toX] = BlackPawn;
10198         if(oldEP & EP_BEROLIN_A) {
10199                 captured = board[fromY][fromX-1];
10200                 board[fromY][fromX-1] = EmptySquare;
10201         }else{  captured = board[fromY][fromX+1];
10202                 board[fromY][fromX+1] = EmptySquare;
10203         }
10204     } else {
10205         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10206         board[fromY][fromX] = EmptySquare;
10207         board[toY][toX] = piece;
10208     }
10209   }
10210
10211     if (gameInfo.holdingsWidth != 0) {
10212
10213       /* !!A lot more code needs to be written to support holdings  */
10214       /* [HGM] OK, so I have written it. Holdings are stored in the */
10215       /* penultimate board files, so they are automaticlly stored   */
10216       /* in the game history.                                       */
10217       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10218                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10219         /* Delete from holdings, by decreasing count */
10220         /* and erasing image if necessary            */
10221         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10222         if(p < (int) BlackPawn) { /* white drop */
10223              p -= (int)WhitePawn;
10224                  p = PieceToNumber((ChessSquare)p);
10225              if(p >= gameInfo.holdingsSize) p = 0;
10226              if(--board[p][BOARD_WIDTH-2] <= 0)
10227                   board[p][BOARD_WIDTH-1] = EmptySquare;
10228              if((int)board[p][BOARD_WIDTH-2] < 0)
10229                         board[p][BOARD_WIDTH-2] = 0;
10230         } else {                  /* black drop */
10231              p -= (int)BlackPawn;
10232                  p = PieceToNumber((ChessSquare)p);
10233              if(p >= gameInfo.holdingsSize) p = 0;
10234              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10235                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10236              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10237                         board[BOARD_HEIGHT-1-p][1] = 0;
10238         }
10239       }
10240       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10241           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10242         /* [HGM] holdings: Add to holdings, if holdings exist */
10243         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10244                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10245                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10246         }
10247         p = (int) captured;
10248         if (p >= (int) BlackPawn) {
10249           p -= (int)BlackPawn;
10250           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10251                   /* Restore shogi-promoted piece to its original  first */
10252                   captured = (ChessSquare) (DEMOTED captured);
10253                   p = DEMOTED p;
10254           }
10255           p = PieceToNumber((ChessSquare)p);
10256           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10257           board[p][BOARD_WIDTH-2]++;
10258           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10259         } else {
10260           p -= (int)WhitePawn;
10261           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10262                   captured = (ChessSquare) (DEMOTED captured);
10263                   p = DEMOTED p;
10264           }
10265           p = PieceToNumber((ChessSquare)p);
10266           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10267           board[BOARD_HEIGHT-1-p][1]++;
10268           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10269         }
10270       }
10271     } else if (gameInfo.variant == VariantAtomic) {
10272       if (captured != EmptySquare) {
10273         int y, x;
10274         for (y = toY-1; y <= toY+1; y++) {
10275           for (x = toX-1; x <= toX+1; x++) {
10276             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10277                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10278               board[y][x] = EmptySquare;
10279             }
10280           }
10281         }
10282         board[toY][toX] = EmptySquare;
10283       }
10284     }
10285
10286     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10287         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10288     } else
10289     if(promoChar == '+') {
10290         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10291         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10292         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10293           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10294     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10295         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10296         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10297            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10298         board[toY][toX] = newPiece;
10299     }
10300     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10301                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10302         // [HGM] superchess: take promotion piece out of holdings
10303         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10304         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10305             if(!--board[k][BOARD_WIDTH-2])
10306                 board[k][BOARD_WIDTH-1] = EmptySquare;
10307         } else {
10308             if(!--board[BOARD_HEIGHT-1-k][1])
10309                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10310         }
10311     }
10312 }
10313
10314 /* Updates forwardMostMove */
10315 void
10316 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10317 {
10318     int x = toX, y = toY;
10319     char *s = parseList[forwardMostMove];
10320     ChessSquare p = boards[forwardMostMove][toY][toX];
10321 //    forwardMostMove++; // [HGM] bare: moved downstream
10322
10323     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10324     (void) CoordsToAlgebraic(boards[forwardMostMove],
10325                              PosFlags(forwardMostMove),
10326                              fromY, fromX, y, x, promoChar,
10327                              s);
10328     if(killX >= 0 && killY >= 0)
10329         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10330
10331     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10332         int timeLeft; static int lastLoadFlag=0; int king, piece;
10333         piece = boards[forwardMostMove][fromY][fromX];
10334         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10335         if(gameInfo.variant == VariantKnightmate)
10336             king += (int) WhiteUnicorn - (int) WhiteKing;
10337         if(forwardMostMove == 0) {
10338             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10339                 fprintf(serverMoves, "%s;", UserName());
10340             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10341                 fprintf(serverMoves, "%s;", second.tidy);
10342             fprintf(serverMoves, "%s;", first.tidy);
10343             if(gameMode == MachinePlaysWhite)
10344                 fprintf(serverMoves, "%s;", UserName());
10345             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10346                 fprintf(serverMoves, "%s;", second.tidy);
10347         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10348         lastLoadFlag = loadFlag;
10349         // print base move
10350         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10351         // print castling suffix
10352         if( toY == fromY && piece == king ) {
10353             if(toX-fromX > 1)
10354                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10355             if(fromX-toX >1)
10356                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10357         }
10358         // e.p. suffix
10359         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10360              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10361              boards[forwardMostMove][toY][toX] == EmptySquare
10362              && fromX != toX && fromY != toY)
10363                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10364         // promotion suffix
10365         if(promoChar != NULLCHAR) {
10366             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10367                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10368                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10369             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10370         }
10371         if(!loadFlag) {
10372                 char buf[MOVE_LEN*2], *p; int len;
10373             fprintf(serverMoves, "/%d/%d",
10374                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10375             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10376             else                      timeLeft = blackTimeRemaining/1000;
10377             fprintf(serverMoves, "/%d", timeLeft);
10378                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10379                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10380                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10381                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10382             fprintf(serverMoves, "/%s", buf);
10383         }
10384         fflush(serverMoves);
10385     }
10386
10387     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10388         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10389       return;
10390     }
10391     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10392     if (commentList[forwardMostMove+1] != NULL) {
10393         free(commentList[forwardMostMove+1]);
10394         commentList[forwardMostMove+1] = NULL;
10395     }
10396     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10397     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10398     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10399     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10400     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10401     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10402     adjustedClock = FALSE;
10403     gameInfo.result = GameUnfinished;
10404     if (gameInfo.resultDetails != NULL) {
10405         free(gameInfo.resultDetails);
10406         gameInfo.resultDetails = NULL;
10407     }
10408     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10409                               moveList[forwardMostMove - 1]);
10410     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10411       case MT_NONE:
10412       case MT_STALEMATE:
10413       default:
10414         break;
10415       case MT_CHECK:
10416         if(!IS_SHOGI(gameInfo.variant))
10417             strcat(parseList[forwardMostMove - 1], "+");
10418         break;
10419       case MT_CHECKMATE:
10420       case MT_STAINMATE:
10421         strcat(parseList[forwardMostMove - 1], "#");
10422         break;
10423     }
10424 }
10425
10426 /* Updates currentMove if not pausing */
10427 void
10428 ShowMove (int fromX, int fromY, int toX, int toY)
10429 {
10430     int instant = (gameMode == PlayFromGameFile) ?
10431         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10432     if(appData.noGUI) return;
10433     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10434         if (!instant) {
10435             if (forwardMostMove == currentMove + 1) {
10436                 AnimateMove(boards[forwardMostMove - 1],
10437                             fromX, fromY, toX, toY);
10438             }
10439         }
10440         currentMove = forwardMostMove;
10441     }
10442
10443     killX = killY = -1; // [HGM] lion: used up
10444
10445     if (instant) return;
10446
10447     DisplayMove(currentMove - 1);
10448     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10449             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10450                 SetHighlights(fromX, fromY, toX, toY);
10451             }
10452     }
10453     DrawPosition(FALSE, boards[currentMove]);
10454     DisplayBothClocks();
10455     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10456 }
10457
10458 void
10459 SendEgtPath (ChessProgramState *cps)
10460 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10461         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10462
10463         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10464
10465         while(*p) {
10466             char c, *q = name+1, *r, *s;
10467
10468             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10469             while(*p && *p != ',') *q++ = *p++;
10470             *q++ = ':'; *q = 0;
10471             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10472                 strcmp(name, ",nalimov:") == 0 ) {
10473                 // take nalimov path from the menu-changeable option first, if it is defined
10474               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10475                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10476             } else
10477             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10478                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10479                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10480                 s = r = StrStr(s, ":") + 1; // beginning of path info
10481                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10482                 c = *r; *r = 0;             // temporarily null-terminate path info
10483                     *--q = 0;               // strip of trailig ':' from name
10484                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10485                 *r = c;
10486                 SendToProgram(buf,cps);     // send egtbpath command for this format
10487             }
10488             if(*p == ',') p++; // read away comma to position for next format name
10489         }
10490 }
10491
10492 static int
10493 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10494 {
10495       int width = 8, height = 8, holdings = 0;             // most common sizes
10496       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10497       // correct the deviations default for each variant
10498       if( v == VariantXiangqi ) width = 9,  height = 10;
10499       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10500       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10501       if( v == VariantCapablanca || v == VariantCapaRandom ||
10502           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10503                                 width = 10;
10504       if( v == VariantCourier ) width = 12;
10505       if( v == VariantSuper )                            holdings = 8;
10506       if( v == VariantGreat )   width = 10,              holdings = 8;
10507       if( v == VariantSChess )                           holdings = 7;
10508       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10509       if( v == VariantChuChess) width = 10, height = 10;
10510       if( v == VariantChu )     width = 12, height = 12;
10511       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10512              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10513              holdingsSize >= 0 && holdingsSize != holdings;
10514 }
10515
10516 char variantError[MSG_SIZ];
10517
10518 char *
10519 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10520 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10521       char *p, *variant = VariantName(v);
10522       static char b[MSG_SIZ];
10523       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10524            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10525                                                holdingsSize, variant); // cook up sized variant name
10526            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10527            if(StrStr(list, b) == NULL) {
10528                // specific sized variant not known, check if general sizing allowed
10529                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10530                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10531                             boardWidth, boardHeight, holdingsSize, engine);
10532                    return NULL;
10533                }
10534                /* [HGM] here we really should compare with the maximum supported board size */
10535            }
10536       } else snprintf(b, MSG_SIZ,"%s", variant);
10537       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10538       p = StrStr(list, b);
10539       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10540       if(p == NULL) {
10541           // occurs not at all in list, or only as sub-string
10542           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10543           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10544               int l = strlen(variantError);
10545               char *q;
10546               while(p != list && p[-1] != ',') p--;
10547               q = strchr(p, ',');
10548               if(q) *q = NULLCHAR;
10549               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10550               if(q) *q= ',';
10551           }
10552           return NULL;
10553       }
10554       return b;
10555 }
10556
10557 void
10558 InitChessProgram (ChessProgramState *cps, int setup)
10559 /* setup needed to setup FRC opening position */
10560 {
10561     char buf[MSG_SIZ], *b;
10562     if (appData.noChessProgram) return;
10563     hintRequested = FALSE;
10564     bookRequested = FALSE;
10565
10566     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10567     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10568     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10569     if(cps->memSize) { /* [HGM] memory */
10570       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10571         SendToProgram(buf, cps);
10572     }
10573     SendEgtPath(cps); /* [HGM] EGT */
10574     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10575       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10576         SendToProgram(buf, cps);
10577     }
10578
10579     setboardSpoiledMachineBlack = FALSE;
10580     SendToProgram(cps->initString, cps);
10581     if (gameInfo.variant != VariantNormal &&
10582         gameInfo.variant != VariantLoadable
10583         /* [HGM] also send variant if board size non-standard */
10584         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10585
10586       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10587                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10588       if (b == NULL) {
10589         VariantClass v;
10590         char c, *q = cps->variants, *p = strchr(q, ',');
10591         if(p) *p = NULLCHAR;
10592         v = StringToVariant(q);
10593         DisplayError(variantError, 0);
10594         if(v != VariantUnknown && cps == &first) {
10595             int w, h, s;
10596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10598             ASSIGN(appData.variant, q);
10599             Reset(TRUE, FALSE);
10600         }
10601         if(p) *p = ',';
10602         return;
10603       }
10604
10605       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10606       SendToProgram(buf, cps);
10607     }
10608     currentlyInitializedVariant = gameInfo.variant;
10609
10610     /* [HGM] send opening position in FRC to first engine */
10611     if(setup) {
10612           SendToProgram("force\n", cps);
10613           SendBoard(cps, 0);
10614           /* engine is now in force mode! Set flag to wake it up after first move. */
10615           setboardSpoiledMachineBlack = 1;
10616     }
10617
10618     if (cps->sendICS) {
10619       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10620       SendToProgram(buf, cps);
10621     }
10622     cps->maybeThinking = FALSE;
10623     cps->offeredDraw = 0;
10624     if (!appData.icsActive) {
10625         SendTimeControl(cps, movesPerSession, timeControl,
10626                         timeIncrement, appData.searchDepth,
10627                         searchTime);
10628     }
10629     if (appData.showThinking
10630         // [HGM] thinking: four options require thinking output to be sent
10631         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10632                                 ) {
10633         SendToProgram("post\n", cps);
10634     }
10635     SendToProgram("hard\n", cps);
10636     if (!appData.ponderNextMove) {
10637         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10638            it without being sure what state we are in first.  "hard"
10639            is not a toggle, so that one is OK.
10640          */
10641         SendToProgram("easy\n", cps);
10642     }
10643     if (cps->usePing) {
10644       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10645       SendToProgram(buf, cps);
10646     }
10647     cps->initDone = TRUE;
10648     ClearEngineOutputPane(cps == &second);
10649 }
10650
10651
10652 void
10653 ResendOptions (ChessProgramState *cps)
10654 { // send the stored value of the options
10655   int i;
10656   char buf[MSG_SIZ];
10657   Option *opt = cps->option;
10658   for(i=0; i<cps->nrOptions; i++, opt++) {
10659       switch(opt->type) {
10660         case Spin:
10661         case Slider:
10662         case CheckBox:
10663             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10664           break;
10665         case ComboBox:
10666           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10667           break;
10668         default:
10669             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10670           break;
10671         case Button:
10672         case SaveButton:
10673           continue;
10674       }
10675       SendToProgram(buf, cps);
10676   }
10677 }
10678
10679 void
10680 StartChessProgram (ChessProgramState *cps)
10681 {
10682     char buf[MSG_SIZ];
10683     int err;
10684
10685     if (appData.noChessProgram) return;
10686     cps->initDone = FALSE;
10687
10688     if (strcmp(cps->host, "localhost") == 0) {
10689         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10690     } else if (*appData.remoteShell == NULLCHAR) {
10691         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10692     } else {
10693         if (*appData.remoteUser == NULLCHAR) {
10694           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10695                     cps->program);
10696         } else {
10697           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10698                     cps->host, appData.remoteUser, cps->program);
10699         }
10700         err = StartChildProcess(buf, "", &cps->pr);
10701     }
10702
10703     if (err != 0) {
10704       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10705         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10706         if(cps != &first) return;
10707         appData.noChessProgram = TRUE;
10708         ThawUI();
10709         SetNCPMode();
10710 //      DisplayFatalError(buf, err, 1);
10711 //      cps->pr = NoProc;
10712 //      cps->isr = NULL;
10713         return;
10714     }
10715
10716     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10717     if (cps->protocolVersion > 1) {
10718       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10719       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10720         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10721         cps->comboCnt = 0;  //                and values of combo boxes
10722       }
10723       SendToProgram(buf, cps);
10724       if(cps->reload) ResendOptions(cps);
10725     } else {
10726       SendToProgram("xboard\n", cps);
10727     }
10728 }
10729
10730 void
10731 TwoMachinesEventIfReady P((void))
10732 {
10733   static int curMess = 0;
10734   if (first.lastPing != first.lastPong) {
10735     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10736     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10737     return;
10738   }
10739   if (second.lastPing != second.lastPong) {
10740     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10741     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10742     return;
10743   }
10744   DisplayMessage("", ""); curMess = 0;
10745   TwoMachinesEvent();
10746 }
10747
10748 char *
10749 MakeName (char *template)
10750 {
10751     time_t clock;
10752     struct tm *tm;
10753     static char buf[MSG_SIZ];
10754     char *p = buf;
10755     int i;
10756
10757     clock = time((time_t *)NULL);
10758     tm = localtime(&clock);
10759
10760     while(*p++ = *template++) if(p[-1] == '%') {
10761         switch(*template++) {
10762           case 0:   *p = 0; return buf;
10763           case 'Y': i = tm->tm_year+1900; break;
10764           case 'y': i = tm->tm_year-100; break;
10765           case 'M': i = tm->tm_mon+1; break;
10766           case 'd': i = tm->tm_mday; break;
10767           case 'h': i = tm->tm_hour; break;
10768           case 'm': i = tm->tm_min; break;
10769           case 's': i = tm->tm_sec; break;
10770           default:  i = 0;
10771         }
10772         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10773     }
10774     return buf;
10775 }
10776
10777 int
10778 CountPlayers (char *p)
10779 {
10780     int n = 0;
10781     while(p = strchr(p, '\n')) p++, n++; // count participants
10782     return n;
10783 }
10784
10785 FILE *
10786 WriteTourneyFile (char *results, FILE *f)
10787 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10788     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10789     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10790         // create a file with tournament description
10791         fprintf(f, "-participants {%s}\n", appData.participants);
10792         fprintf(f, "-seedBase %d\n", appData.seedBase);
10793         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10794         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10795         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10796         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10797         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10798         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10799         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10800         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10801         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10802         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10803         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10804         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10805         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10806         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10807         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10808         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10809         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10810         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10811         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10812         fprintf(f, "-smpCores %d\n", appData.smpCores);
10813         if(searchTime > 0)
10814                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10815         else {
10816                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10817                 fprintf(f, "-tc %s\n", appData.timeControl);
10818                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10819         }
10820         fprintf(f, "-results \"%s\"\n", results);
10821     }
10822     return f;
10823 }
10824
10825 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10826
10827 void
10828 Substitute (char *participants, int expunge)
10829 {
10830     int i, changed, changes=0, nPlayers=0;
10831     char *p, *q, *r, buf[MSG_SIZ];
10832     if(participants == NULL) return;
10833     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10834     r = p = participants; q = appData.participants;
10835     while(*p && *p == *q) {
10836         if(*p == '\n') r = p+1, nPlayers++;
10837         p++; q++;
10838     }
10839     if(*p) { // difference
10840         while(*p && *p++ != '\n');
10841         while(*q && *q++ != '\n');
10842       changed = nPlayers;
10843         changes = 1 + (strcmp(p, q) != 0);
10844     }
10845     if(changes == 1) { // a single engine mnemonic was changed
10846         q = r; while(*q) nPlayers += (*q++ == '\n');
10847         p = buf; while(*r && (*p = *r++) != '\n') p++;
10848         *p = NULLCHAR;
10849         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10850         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10851         if(mnemonic[i]) { // The substitute is valid
10852             FILE *f;
10853             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10854                 flock(fileno(f), LOCK_EX);
10855                 ParseArgsFromFile(f);
10856                 fseek(f, 0, SEEK_SET);
10857                 FREE(appData.participants); appData.participants = participants;
10858                 if(expunge) { // erase results of replaced engine
10859                     int len = strlen(appData.results), w, b, dummy;
10860                     for(i=0; i<len; i++) {
10861                         Pairing(i, nPlayers, &w, &b, &dummy);
10862                         if((w == changed || b == changed) && appData.results[i] == '*') {
10863                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10864                             fclose(f);
10865                             return;
10866                         }
10867                     }
10868                     for(i=0; i<len; i++) {
10869                         Pairing(i, nPlayers, &w, &b, &dummy);
10870                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10871                     }
10872                 }
10873                 WriteTourneyFile(appData.results, f);
10874                 fclose(f); // release lock
10875                 return;
10876             }
10877         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10878     }
10879     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10880     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10881     free(participants);
10882     return;
10883 }
10884
10885 int
10886 CheckPlayers (char *participants)
10887 {
10888         int i;
10889         char buf[MSG_SIZ], *p;
10890         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10891         while(p = strchr(participants, '\n')) {
10892             *p = NULLCHAR;
10893             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10894             if(!mnemonic[i]) {
10895                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10896                 *p = '\n';
10897                 DisplayError(buf, 0);
10898                 return 1;
10899             }
10900             *p = '\n';
10901             participants = p + 1;
10902         }
10903         return 0;
10904 }
10905
10906 int
10907 CreateTourney (char *name)
10908 {
10909         FILE *f;
10910         if(matchMode && strcmp(name, appData.tourneyFile)) {
10911              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10912         }
10913         if(name[0] == NULLCHAR) {
10914             if(appData.participants[0])
10915                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10916             return 0;
10917         }
10918         f = fopen(name, "r");
10919         if(f) { // file exists
10920             ASSIGN(appData.tourneyFile, name);
10921             ParseArgsFromFile(f); // parse it
10922         } else {
10923             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10924             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10925                 DisplayError(_("Not enough participants"), 0);
10926                 return 0;
10927             }
10928             if(CheckPlayers(appData.participants)) return 0;
10929             ASSIGN(appData.tourneyFile, name);
10930             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10931             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10932         }
10933         fclose(f);
10934         appData.noChessProgram = FALSE;
10935         appData.clockMode = TRUE;
10936         SetGNUMode();
10937         return 1;
10938 }
10939
10940 int
10941 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10942 {
10943     char buf[MSG_SIZ], *p, *q;
10944     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10945     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10946     skip = !all && group[0]; // if group requested, we start in skip mode
10947     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10948         p = names; q = buf; header = 0;
10949         while(*p && *p != '\n') *q++ = *p++;
10950         *q = 0;
10951         if(*p == '\n') p++;
10952         if(buf[0] == '#') {
10953             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10954             depth++; // we must be entering a new group
10955             if(all) continue; // suppress printing group headers when complete list requested
10956             header = 1;
10957             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10958         }
10959         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10960         if(engineList[i]) free(engineList[i]);
10961         engineList[i] = strdup(buf);
10962         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10963         if(engineMnemonic[i]) free(engineMnemonic[i]);
10964         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10965             strcat(buf, " (");
10966             sscanf(q + 8, "%s", buf + strlen(buf));
10967             strcat(buf, ")");
10968         }
10969         engineMnemonic[i] = strdup(buf);
10970         i++;
10971     }
10972     engineList[i] = engineMnemonic[i] = NULL;
10973     return i;
10974 }
10975
10976 // following implemented as macro to avoid type limitations
10977 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10978
10979 void
10980 SwapEngines (int n)
10981 {   // swap settings for first engine and other engine (so far only some selected options)
10982     int h;
10983     char *p;
10984     if(n == 0) return;
10985     SWAP(directory, p)
10986     SWAP(chessProgram, p)
10987     SWAP(isUCI, h)
10988     SWAP(hasOwnBookUCI, h)
10989     SWAP(protocolVersion, h)
10990     SWAP(reuse, h)
10991     SWAP(scoreIsAbsolute, h)
10992     SWAP(timeOdds, h)
10993     SWAP(logo, p)
10994     SWAP(pgnName, p)
10995     SWAP(pvSAN, h)
10996     SWAP(engOptions, p)
10997     SWAP(engInitString, p)
10998     SWAP(computerString, p)
10999     SWAP(features, p)
11000     SWAP(fenOverride, p)
11001     SWAP(NPS, h)
11002     SWAP(accumulateTC, h)
11003     SWAP(drawDepth, h)
11004     SWAP(host, p)
11005     SWAP(pseudo, h)
11006 }
11007
11008 int
11009 GetEngineLine (char *s, int n)
11010 {
11011     int i;
11012     char buf[MSG_SIZ];
11013     extern char *icsNames;
11014     if(!s || !*s) return 0;
11015     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11016     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11017     if(!mnemonic[i]) return 0;
11018     if(n == 11) return 1; // just testing if there was a match
11019     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11020     if(n == 1) SwapEngines(n);
11021     ParseArgsFromString(buf);
11022     if(n == 1) SwapEngines(n);
11023     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11024         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11025         ParseArgsFromString(buf);
11026     }
11027     return 1;
11028 }
11029
11030 int
11031 SetPlayer (int player, char *p)
11032 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11033     int i;
11034     char buf[MSG_SIZ], *engineName;
11035     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11036     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11037     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11038     if(mnemonic[i]) {
11039         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11040         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11041         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11042         ParseArgsFromString(buf);
11043     } else { // no engine with this nickname is installed!
11044         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11045         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11046         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11047         ModeHighlight();
11048         DisplayError(buf, 0);
11049         return 0;
11050     }
11051     free(engineName);
11052     return i;
11053 }
11054
11055 char *recentEngines;
11056
11057 void
11058 RecentEngineEvent (int nr)
11059 {
11060     int n;
11061 //    SwapEngines(1); // bump first to second
11062 //    ReplaceEngine(&second, 1); // and load it there
11063     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11064     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11065     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11066         ReplaceEngine(&first, 0);
11067         FloatToFront(&appData.recentEngineList, command[n]);
11068     }
11069 }
11070
11071 int
11072 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11073 {   // determine players from game number
11074     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11075
11076     if(appData.tourneyType == 0) {
11077         roundsPerCycle = (nPlayers - 1) | 1;
11078         pairingsPerRound = nPlayers / 2;
11079     } else if(appData.tourneyType > 0) {
11080         roundsPerCycle = nPlayers - appData.tourneyType;
11081         pairingsPerRound = appData.tourneyType;
11082     }
11083     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11084     gamesPerCycle = gamesPerRound * roundsPerCycle;
11085     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11086     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11087     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11088     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11089     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11090     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11091
11092     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11093     if(appData.roundSync) *syncInterval = gamesPerRound;
11094
11095     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11096
11097     if(appData.tourneyType == 0) {
11098         if(curPairing == (nPlayers-1)/2 ) {
11099             *whitePlayer = curRound;
11100             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11101         } else {
11102             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11103             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11104             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11105             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11106         }
11107     } else if(appData.tourneyType > 1) {
11108         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11109         *whitePlayer = curRound + appData.tourneyType;
11110     } else if(appData.tourneyType > 0) {
11111         *whitePlayer = curPairing;
11112         *blackPlayer = curRound + appData.tourneyType;
11113     }
11114
11115     // take care of white/black alternation per round.
11116     // For cycles and games this is already taken care of by default, derived from matchGame!
11117     return curRound & 1;
11118 }
11119
11120 int
11121 NextTourneyGame (int nr, int *swapColors)
11122 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11123     char *p, *q;
11124     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11125     FILE *tf;
11126     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11127     tf = fopen(appData.tourneyFile, "r");
11128     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11129     ParseArgsFromFile(tf); fclose(tf);
11130     InitTimeControls(); // TC might be altered from tourney file
11131
11132     nPlayers = CountPlayers(appData.participants); // count participants
11133     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11134     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11135
11136     if(syncInterval) {
11137         p = q = appData.results;
11138         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11139         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11140             DisplayMessage(_("Waiting for other game(s)"),"");
11141             waitingForGame = TRUE;
11142             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11143             return 0;
11144         }
11145         waitingForGame = FALSE;
11146     }
11147
11148     if(appData.tourneyType < 0) {
11149         if(nr>=0 && !pairingReceived) {
11150             char buf[1<<16];
11151             if(pairing.pr == NoProc) {
11152                 if(!appData.pairingEngine[0]) {
11153                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11154                     return 0;
11155                 }
11156                 StartChessProgram(&pairing); // starts the pairing engine
11157             }
11158             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11159             SendToProgram(buf, &pairing);
11160             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11161             SendToProgram(buf, &pairing);
11162             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11163         }
11164         pairingReceived = 0;                              // ... so we continue here
11165         *swapColors = 0;
11166         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11167         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11168         matchGame = 1; roundNr = nr / syncInterval + 1;
11169     }
11170
11171     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11172
11173     // redefine engines, engine dir, etc.
11174     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11175     if(first.pr == NoProc) {
11176       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11177       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11178     }
11179     if(second.pr == NoProc) {
11180       SwapEngines(1);
11181       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11182       SwapEngines(1);         // and make that valid for second engine by swapping
11183       InitEngine(&second, 1);
11184     }
11185     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11186     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11187     return OK;
11188 }
11189
11190 void
11191 NextMatchGame ()
11192 {   // performs game initialization that does not invoke engines, and then tries to start the game
11193     int res, firstWhite, swapColors = 0;
11194     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11195     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
11196         char buf[MSG_SIZ];
11197         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11198         if(strcmp(buf, currentDebugFile)) { // name has changed
11199             FILE *f = fopen(buf, "w");
11200             if(f) { // if opening the new file failed, just keep using the old one
11201                 ASSIGN(currentDebugFile, buf);
11202                 fclose(debugFP);
11203                 debugFP = f;
11204             }
11205             if(appData.serverFileName) {
11206                 if(serverFP) fclose(serverFP);
11207                 serverFP = fopen(appData.serverFileName, "w");
11208                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11209                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11210             }
11211         }
11212     }
11213     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11214     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11215     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11216     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11217     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11218     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11219     Reset(FALSE, first.pr != NoProc);
11220     res = LoadGameOrPosition(matchGame); // setup game
11221     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11222     if(!res) return; // abort when bad game/pos file
11223     TwoMachinesEvent();
11224 }
11225
11226 void
11227 UserAdjudicationEvent (int result)
11228 {
11229     ChessMove gameResult = GameIsDrawn;
11230
11231     if( result > 0 ) {
11232         gameResult = WhiteWins;
11233     }
11234     else if( result < 0 ) {
11235         gameResult = BlackWins;
11236     }
11237
11238     if( gameMode == TwoMachinesPlay ) {
11239         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11240     }
11241 }
11242
11243
11244 // [HGM] save: calculate checksum of game to make games easily identifiable
11245 int
11246 StringCheckSum (char *s)
11247 {
11248         int i = 0;
11249         if(s==NULL) return 0;
11250         while(*s) i = i*259 + *s++;
11251         return i;
11252 }
11253
11254 int
11255 GameCheckSum ()
11256 {
11257         int i, sum=0;
11258         for(i=backwardMostMove; i<forwardMostMove; i++) {
11259                 sum += pvInfoList[i].depth;
11260                 sum += StringCheckSum(parseList[i]);
11261                 sum += StringCheckSum(commentList[i]);
11262                 sum *= 261;
11263         }
11264         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11265         return sum + StringCheckSum(commentList[i]);
11266 } // end of save patch
11267
11268 void
11269 GameEnds (ChessMove result, char *resultDetails, int whosays)
11270 {
11271     GameMode nextGameMode;
11272     int isIcsGame;
11273     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11274
11275     if(endingGame) return; /* [HGM] crash: forbid recursion */
11276     endingGame = 1;
11277     if(twoBoards) { // [HGM] dual: switch back to one board
11278         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11279         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11280     }
11281     if (appData.debugMode) {
11282       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11283               result, resultDetails ? resultDetails : "(null)", whosays);
11284     }
11285
11286     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11287
11288     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11289
11290     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11291         /* If we are playing on ICS, the server decides when the
11292            game is over, but the engine can offer to draw, claim
11293            a draw, or resign.
11294          */
11295 #if ZIPPY
11296         if (appData.zippyPlay && first.initDone) {
11297             if (result == GameIsDrawn) {
11298                 /* In case draw still needs to be claimed */
11299                 SendToICS(ics_prefix);
11300                 SendToICS("draw\n");
11301             } else if (StrCaseStr(resultDetails, "resign")) {
11302                 SendToICS(ics_prefix);
11303                 SendToICS("resign\n");
11304             }
11305         }
11306 #endif
11307         endingGame = 0; /* [HGM] crash */
11308         return;
11309     }
11310
11311     /* If we're loading the game from a file, stop */
11312     if (whosays == GE_FILE) {
11313       (void) StopLoadGameTimer();
11314       gameFileFP = NULL;
11315     }
11316
11317     /* Cancel draw offers */
11318     first.offeredDraw = second.offeredDraw = 0;
11319
11320     /* If this is an ICS game, only ICS can really say it's done;
11321        if not, anyone can. */
11322     isIcsGame = (gameMode == IcsPlayingWhite ||
11323                  gameMode == IcsPlayingBlack ||
11324                  gameMode == IcsObserving    ||
11325                  gameMode == IcsExamining);
11326
11327     if (!isIcsGame || whosays == GE_ICS) {
11328         /* OK -- not an ICS game, or ICS said it was done */
11329         StopClocks();
11330         if (!isIcsGame && !appData.noChessProgram)
11331           SetUserThinkingEnables();
11332
11333         /* [HGM] if a machine claims the game end we verify this claim */
11334         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11335             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11336                 char claimer;
11337                 ChessMove trueResult = (ChessMove) -1;
11338
11339                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11340                                             first.twoMachinesColor[0] :
11341                                             second.twoMachinesColor[0] ;
11342
11343                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11344                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11345                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11346                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11347                 } else
11348                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11349                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11350                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11351                 } else
11352                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11353                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11354                 }
11355
11356                 // now verify win claims, but not in drop games, as we don't understand those yet
11357                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11358                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11359                     (result == WhiteWins && claimer == 'w' ||
11360                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11361                       if (appData.debugMode) {
11362                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11363                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11364                       }
11365                       if(result != trueResult) {
11366                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11367                               result = claimer == 'w' ? BlackWins : WhiteWins;
11368                               resultDetails = buf;
11369                       }
11370                 } else
11371                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11372                     && (forwardMostMove <= backwardMostMove ||
11373                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11374                         (claimer=='b')==(forwardMostMove&1))
11375                                                                                   ) {
11376                       /* [HGM] verify: draws that were not flagged are false claims */
11377                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11378                       result = claimer == 'w' ? BlackWins : WhiteWins;
11379                       resultDetails = buf;
11380                 }
11381                 /* (Claiming a loss is accepted no questions asked!) */
11382             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11383                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11384                 result = GameUnfinished;
11385                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11386             }
11387             /* [HGM] bare: don't allow bare King to win */
11388             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11389                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11390                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11391                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11392                && result != GameIsDrawn)
11393             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11394                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11395                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11396                         if(p >= 0 && p <= (int)WhiteKing) k++;
11397                 }
11398                 if (appData.debugMode) {
11399                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11400                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11401                 }
11402                 if(k <= 1) {
11403                         result = GameIsDrawn;
11404                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11405                         resultDetails = buf;
11406                 }
11407             }
11408         }
11409
11410
11411         if(serverMoves != NULL && !loadFlag) { char c = '=';
11412             if(result==WhiteWins) c = '+';
11413             if(result==BlackWins) c = '-';
11414             if(resultDetails != NULL)
11415                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11416         }
11417         if (resultDetails != NULL) {
11418             gameInfo.result = result;
11419             gameInfo.resultDetails = StrSave(resultDetails);
11420
11421             /* display last move only if game was not loaded from file */
11422             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11423                 DisplayMove(currentMove - 1);
11424
11425             if (forwardMostMove != 0) {
11426                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11427                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11428                                                                 ) {
11429                     if (*appData.saveGameFile != NULLCHAR) {
11430                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11431                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11432                         else
11433                         SaveGameToFile(appData.saveGameFile, TRUE);
11434                     } else if (appData.autoSaveGames) {
11435                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11436                     }
11437                     if (*appData.savePositionFile != NULLCHAR) {
11438                         SavePositionToFile(appData.savePositionFile);
11439                     }
11440                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11441                 }
11442             }
11443
11444             /* Tell program how game ended in case it is learning */
11445             /* [HGM] Moved this to after saving the PGN, just in case */
11446             /* engine died and we got here through time loss. In that */
11447             /* case we will get a fatal error writing the pipe, which */
11448             /* would otherwise lose us the PGN.                       */
11449             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11450             /* output during GameEnds should never be fatal anymore   */
11451             if (gameMode == MachinePlaysWhite ||
11452                 gameMode == MachinePlaysBlack ||
11453                 gameMode == TwoMachinesPlay ||
11454                 gameMode == IcsPlayingWhite ||
11455                 gameMode == IcsPlayingBlack ||
11456                 gameMode == BeginningOfGame) {
11457                 char buf[MSG_SIZ];
11458                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11459                         resultDetails);
11460                 if (first.pr != NoProc) {
11461                     SendToProgram(buf, &first);
11462                 }
11463                 if (second.pr != NoProc &&
11464                     gameMode == TwoMachinesPlay) {
11465                     SendToProgram(buf, &second);
11466                 }
11467             }
11468         }
11469
11470         if (appData.icsActive) {
11471             if (appData.quietPlay &&
11472                 (gameMode == IcsPlayingWhite ||
11473                  gameMode == IcsPlayingBlack)) {
11474                 SendToICS(ics_prefix);
11475                 SendToICS("set shout 1\n");
11476             }
11477             nextGameMode = IcsIdle;
11478             ics_user_moved = FALSE;
11479             /* clean up premove.  It's ugly when the game has ended and the
11480              * premove highlights are still on the board.
11481              */
11482             if (gotPremove) {
11483               gotPremove = FALSE;
11484               ClearPremoveHighlights();
11485               DrawPosition(FALSE, boards[currentMove]);
11486             }
11487             if (whosays == GE_ICS) {
11488                 switch (result) {
11489                 case WhiteWins:
11490                     if (gameMode == IcsPlayingWhite)
11491                         PlayIcsWinSound();
11492                     else if(gameMode == IcsPlayingBlack)
11493                         PlayIcsLossSound();
11494                     break;
11495                 case BlackWins:
11496                     if (gameMode == IcsPlayingBlack)
11497                         PlayIcsWinSound();
11498                     else if(gameMode == IcsPlayingWhite)
11499                         PlayIcsLossSound();
11500                     break;
11501                 case GameIsDrawn:
11502                     PlayIcsDrawSound();
11503                     break;
11504                 default:
11505                     PlayIcsUnfinishedSound();
11506                 }
11507             }
11508             if(appData.quitNext) { ExitEvent(0); return; }
11509         } else if (gameMode == EditGame ||
11510                    gameMode == PlayFromGameFile ||
11511                    gameMode == AnalyzeMode ||
11512                    gameMode == AnalyzeFile) {
11513             nextGameMode = gameMode;
11514         } else {
11515             nextGameMode = EndOfGame;
11516         }
11517         pausing = FALSE;
11518         ModeHighlight();
11519     } else {
11520         nextGameMode = gameMode;
11521     }
11522
11523     if (appData.noChessProgram) {
11524         gameMode = nextGameMode;
11525         ModeHighlight();
11526         endingGame = 0; /* [HGM] crash */
11527         return;
11528     }
11529
11530     if (first.reuse) {
11531         /* Put first chess program into idle state */
11532         if (first.pr != NoProc &&
11533             (gameMode == MachinePlaysWhite ||
11534              gameMode == MachinePlaysBlack ||
11535              gameMode == TwoMachinesPlay ||
11536              gameMode == IcsPlayingWhite ||
11537              gameMode == IcsPlayingBlack ||
11538              gameMode == BeginningOfGame)) {
11539             SendToProgram("force\n", &first);
11540             if (first.usePing) {
11541               char buf[MSG_SIZ];
11542               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11543               SendToProgram(buf, &first);
11544             }
11545         }
11546     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11547         /* Kill off first chess program */
11548         if (first.isr != NULL)
11549           RemoveInputSource(first.isr);
11550         first.isr = NULL;
11551
11552         if (first.pr != NoProc) {
11553             ExitAnalyzeMode();
11554             DoSleep( appData.delayBeforeQuit );
11555             SendToProgram("quit\n", &first);
11556             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11557             first.reload = TRUE;
11558         }
11559         first.pr = NoProc;
11560     }
11561     if (second.reuse) {
11562         /* Put second chess program into idle state */
11563         if (second.pr != NoProc &&
11564             gameMode == TwoMachinesPlay) {
11565             SendToProgram("force\n", &second);
11566             if (second.usePing) {
11567               char buf[MSG_SIZ];
11568               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11569               SendToProgram(buf, &second);
11570             }
11571         }
11572     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11573         /* Kill off second chess program */
11574         if (second.isr != NULL)
11575           RemoveInputSource(second.isr);
11576         second.isr = NULL;
11577
11578         if (second.pr != NoProc) {
11579             DoSleep( appData.delayBeforeQuit );
11580             SendToProgram("quit\n", &second);
11581             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11582             second.reload = TRUE;
11583         }
11584         second.pr = NoProc;
11585     }
11586
11587     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11588         char resChar = '=';
11589         switch (result) {
11590         case WhiteWins:
11591           resChar = '+';
11592           if (first.twoMachinesColor[0] == 'w') {
11593             first.matchWins++;
11594           } else {
11595             second.matchWins++;
11596           }
11597           break;
11598         case BlackWins:
11599           resChar = '-';
11600           if (first.twoMachinesColor[0] == 'b') {
11601             first.matchWins++;
11602           } else {
11603             second.matchWins++;
11604           }
11605           break;
11606         case GameUnfinished:
11607           resChar = ' ';
11608         default:
11609           break;
11610         }
11611
11612         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11613         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11614             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11615             ReserveGame(nextGame, resChar); // sets nextGame
11616             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11617             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11618         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11619
11620         if (nextGame <= appData.matchGames && !abortMatch) {
11621             gameMode = nextGameMode;
11622             matchGame = nextGame; // this will be overruled in tourney mode!
11623             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11624             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11625             endingGame = 0; /* [HGM] crash */
11626             return;
11627         } else {
11628             gameMode = nextGameMode;
11629             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11630                      first.tidy, second.tidy,
11631                      first.matchWins, second.matchWins,
11632                      appData.matchGames - (first.matchWins + second.matchWins));
11633             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11634             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11635             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11636             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11637                 first.twoMachinesColor = "black\n";
11638                 second.twoMachinesColor = "white\n";
11639             } else {
11640                 first.twoMachinesColor = "white\n";
11641                 second.twoMachinesColor = "black\n";
11642             }
11643         }
11644     }
11645     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11646         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11647       ExitAnalyzeMode();
11648     gameMode = nextGameMode;
11649     ModeHighlight();
11650     endingGame = 0;  /* [HGM] crash */
11651     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11652         if(matchMode == TRUE) { // match through command line: exit with or without popup
11653             if(ranking) {
11654                 ToNrEvent(forwardMostMove);
11655                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11656                 else ExitEvent(0);
11657             } else DisplayFatalError(buf, 0, 0);
11658         } else { // match through menu; just stop, with or without popup
11659             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11660             ModeHighlight();
11661             if(ranking){
11662                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11663             } else DisplayNote(buf);
11664       }
11665       if(ranking) free(ranking);
11666     }
11667 }
11668
11669 /* Assumes program was just initialized (initString sent).
11670    Leaves program in force mode. */
11671 void
11672 FeedMovesToProgram (ChessProgramState *cps, int upto)
11673 {
11674     int i;
11675
11676     if (appData.debugMode)
11677       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11678               startedFromSetupPosition ? "position and " : "",
11679               backwardMostMove, upto, cps->which);
11680     if(currentlyInitializedVariant != gameInfo.variant) {
11681       char buf[MSG_SIZ];
11682         // [HGM] variantswitch: make engine aware of new variant
11683         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11684                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11685                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11686         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11687         SendToProgram(buf, cps);
11688         currentlyInitializedVariant = gameInfo.variant;
11689     }
11690     SendToProgram("force\n", cps);
11691     if (startedFromSetupPosition) {
11692         SendBoard(cps, backwardMostMove);
11693     if (appData.debugMode) {
11694         fprintf(debugFP, "feedMoves\n");
11695     }
11696     }
11697     for (i = backwardMostMove; i < upto; i++) {
11698         SendMoveToProgram(i, cps);
11699     }
11700 }
11701
11702
11703 int
11704 ResurrectChessProgram ()
11705 {
11706      /* The chess program may have exited.
11707         If so, restart it and feed it all the moves made so far. */
11708     static int doInit = 0;
11709
11710     if (appData.noChessProgram) return 1;
11711
11712     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11713         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11714         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11715         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11716     } else {
11717         if (first.pr != NoProc) return 1;
11718         StartChessProgram(&first);
11719     }
11720     InitChessProgram(&first, FALSE);
11721     FeedMovesToProgram(&first, currentMove);
11722
11723     if (!first.sendTime) {
11724         /* can't tell gnuchess what its clock should read,
11725            so we bow to its notion. */
11726         ResetClocks();
11727         timeRemaining[0][currentMove] = whiteTimeRemaining;
11728         timeRemaining[1][currentMove] = blackTimeRemaining;
11729     }
11730
11731     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11732                 appData.icsEngineAnalyze) && first.analysisSupport) {
11733       SendToProgram("analyze\n", &first);
11734       first.analyzing = TRUE;
11735     }
11736     return 1;
11737 }
11738
11739 /*
11740  * Button procedures
11741  */
11742 void
11743 Reset (int redraw, int init)
11744 {
11745     int i;
11746
11747     if (appData.debugMode) {
11748         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11749                 redraw, init, gameMode);
11750     }
11751     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11752     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11753     CleanupTail(); // [HGM] vari: delete any stored variations
11754     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11755     pausing = pauseExamInvalid = FALSE;
11756     startedFromSetupPosition = blackPlaysFirst = FALSE;
11757     firstMove = TRUE;
11758     whiteFlag = blackFlag = FALSE;
11759     userOfferedDraw = FALSE;
11760     hintRequested = bookRequested = FALSE;
11761     first.maybeThinking = FALSE;
11762     second.maybeThinking = FALSE;
11763     first.bookSuspend = FALSE; // [HGM] book
11764     second.bookSuspend = FALSE;
11765     thinkOutput[0] = NULLCHAR;
11766     lastHint[0] = NULLCHAR;
11767     ClearGameInfo(&gameInfo);
11768     gameInfo.variant = StringToVariant(appData.variant);
11769     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11770     ics_user_moved = ics_clock_paused = FALSE;
11771     ics_getting_history = H_FALSE;
11772     ics_gamenum = -1;
11773     white_holding[0] = black_holding[0] = NULLCHAR;
11774     ClearProgramStats();
11775     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11776
11777     ResetFrontEnd();
11778     ClearHighlights();
11779     flipView = appData.flipView;
11780     ClearPremoveHighlights();
11781     gotPremove = FALSE;
11782     alarmSounded = FALSE;
11783     killX = killY = -1; // [HGM] lion
11784
11785     GameEnds(EndOfFile, NULL, GE_PLAYER);
11786     if(appData.serverMovesName != NULL) {
11787         /* [HGM] prepare to make moves file for broadcasting */
11788         clock_t t = clock();
11789         if(serverMoves != NULL) fclose(serverMoves);
11790         serverMoves = fopen(appData.serverMovesName, "r");
11791         if(serverMoves != NULL) {
11792             fclose(serverMoves);
11793             /* delay 15 sec before overwriting, so all clients can see end */
11794             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11795         }
11796         serverMoves = fopen(appData.serverMovesName, "w");
11797     }
11798
11799     ExitAnalyzeMode();
11800     gameMode = BeginningOfGame;
11801     ModeHighlight();
11802     if(appData.icsActive) gameInfo.variant = VariantNormal;
11803     currentMove = forwardMostMove = backwardMostMove = 0;
11804     MarkTargetSquares(1);
11805     InitPosition(redraw);
11806     for (i = 0; i < MAX_MOVES; i++) {
11807         if (commentList[i] != NULL) {
11808             free(commentList[i]);
11809             commentList[i] = NULL;
11810         }
11811     }
11812     ResetClocks();
11813     timeRemaining[0][0] = whiteTimeRemaining;
11814     timeRemaining[1][0] = blackTimeRemaining;
11815
11816     if (first.pr == NoProc) {
11817         StartChessProgram(&first);
11818     }
11819     if (init) {
11820             InitChessProgram(&first, startedFromSetupPosition);
11821     }
11822     DisplayTitle("");
11823     DisplayMessage("", "");
11824     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11825     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11826     ClearMap();        // [HGM] exclude: invalidate map
11827 }
11828
11829 void
11830 AutoPlayGameLoop ()
11831 {
11832     for (;;) {
11833         if (!AutoPlayOneMove())
11834           return;
11835         if (matchMode || appData.timeDelay == 0)
11836           continue;
11837         if (appData.timeDelay < 0)
11838           return;
11839         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11840         break;
11841     }
11842 }
11843
11844 void
11845 AnalyzeNextGame()
11846 {
11847     ReloadGame(1); // next game
11848 }
11849
11850 int
11851 AutoPlayOneMove ()
11852 {
11853     int fromX, fromY, toX, toY;
11854
11855     if (appData.debugMode) {
11856       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11857     }
11858
11859     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11860       return FALSE;
11861
11862     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11863       pvInfoList[currentMove].depth = programStats.depth;
11864       pvInfoList[currentMove].score = programStats.score;
11865       pvInfoList[currentMove].time  = 0;
11866       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11867       else { // append analysis of final position as comment
11868         char buf[MSG_SIZ];
11869         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11870         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11871       }
11872       programStats.depth = 0;
11873     }
11874
11875     if (currentMove >= forwardMostMove) {
11876       if(gameMode == AnalyzeFile) {
11877           if(appData.loadGameIndex == -1) {
11878             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11879           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11880           } else {
11881           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11882         }
11883       }
11884 //      gameMode = EndOfGame;
11885 //      ModeHighlight();
11886
11887       /* [AS] Clear current move marker at the end of a game */
11888       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11889
11890       return FALSE;
11891     }
11892
11893     toX = moveList[currentMove][2] - AAA;
11894     toY = moveList[currentMove][3] - ONE;
11895
11896     if (moveList[currentMove][1] == '@') {
11897         if (appData.highlightLastMove) {
11898             SetHighlights(-1, -1, toX, toY);
11899         }
11900     } else {
11901         int viaX = moveList[currentMove][5] - AAA;
11902         int viaY = moveList[currentMove][6] - ONE;
11903         fromX = moveList[currentMove][0] - AAA;
11904         fromY = moveList[currentMove][1] - ONE;
11905
11906         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11907
11908         if(moveList[currentMove][4] == ';') { // multi-leg
11909             ChessSquare piece = boards[currentMove][viaY][viaX];
11910             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11911             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11912             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11913             boards[currentMove][viaY][viaX] = piece;
11914         } else
11915         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11916
11917         if (appData.highlightLastMove) {
11918             SetHighlights(fromX, fromY, toX, toY);
11919         }
11920     }
11921     DisplayMove(currentMove);
11922     SendMoveToProgram(currentMove++, &first);
11923     DisplayBothClocks();
11924     DrawPosition(FALSE, boards[currentMove]);
11925     // [HGM] PV info: always display, routine tests if empty
11926     DisplayComment(currentMove - 1, commentList[currentMove]);
11927     return TRUE;
11928 }
11929
11930
11931 int
11932 LoadGameOneMove (ChessMove readAhead)
11933 {
11934     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11935     char promoChar = NULLCHAR;
11936     ChessMove moveType;
11937     char move[MSG_SIZ];
11938     char *p, *q;
11939
11940     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11941         gameMode != AnalyzeMode && gameMode != Training) {
11942         gameFileFP = NULL;
11943         return FALSE;
11944     }
11945
11946     yyboardindex = forwardMostMove;
11947     if (readAhead != EndOfFile) {
11948       moveType = readAhead;
11949     } else {
11950       if (gameFileFP == NULL)
11951           return FALSE;
11952       moveType = (ChessMove) Myylex();
11953     }
11954
11955     done = FALSE;
11956     switch (moveType) {
11957       case Comment:
11958         if (appData.debugMode)
11959           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11960         p = yy_text;
11961
11962         /* append the comment but don't display it */
11963         AppendComment(currentMove, p, FALSE);
11964         return TRUE;
11965
11966       case WhiteCapturesEnPassant:
11967       case BlackCapturesEnPassant:
11968       case WhitePromotion:
11969       case BlackPromotion:
11970       case WhiteNonPromotion:
11971       case BlackNonPromotion:
11972       case NormalMove:
11973       case FirstLeg:
11974       case WhiteKingSideCastle:
11975       case WhiteQueenSideCastle:
11976       case BlackKingSideCastle:
11977       case BlackQueenSideCastle:
11978       case WhiteKingSideCastleWild:
11979       case WhiteQueenSideCastleWild:
11980       case BlackKingSideCastleWild:
11981       case BlackQueenSideCastleWild:
11982       /* PUSH Fabien */
11983       case WhiteHSideCastleFR:
11984       case WhiteASideCastleFR:
11985       case BlackHSideCastleFR:
11986       case BlackASideCastleFR:
11987       /* POP Fabien */
11988         if (appData.debugMode)
11989           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11990         fromX = currentMoveString[0] - AAA;
11991         fromY = currentMoveString[1] - ONE;
11992         toX = currentMoveString[2] - AAA;
11993         toY = currentMoveString[3] - ONE;
11994         promoChar = currentMoveString[4];
11995         if(promoChar == ';') promoChar = NULLCHAR;
11996         break;
11997
11998       case WhiteDrop:
11999       case BlackDrop:
12000         if (appData.debugMode)
12001           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12002         fromX = moveType == WhiteDrop ?
12003           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12004         (int) CharToPiece(ToLower(currentMoveString[0]));
12005         fromY = DROP_RANK;
12006         toX = currentMoveString[2] - AAA;
12007         toY = currentMoveString[3] - ONE;
12008         break;
12009
12010       case WhiteWins:
12011       case BlackWins:
12012       case GameIsDrawn:
12013       case GameUnfinished:
12014         if (appData.debugMode)
12015           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12016         p = strchr(yy_text, '{');
12017         if (p == NULL) p = strchr(yy_text, '(');
12018         if (p == NULL) {
12019             p = yy_text;
12020             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12021         } else {
12022             q = strchr(p, *p == '{' ? '}' : ')');
12023             if (q != NULL) *q = NULLCHAR;
12024             p++;
12025         }
12026         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12027         GameEnds(moveType, p, GE_FILE);
12028         done = TRUE;
12029         if (cmailMsgLoaded) {
12030             ClearHighlights();
12031             flipView = WhiteOnMove(currentMove);
12032             if (moveType == GameUnfinished) flipView = !flipView;
12033             if (appData.debugMode)
12034               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12035         }
12036         break;
12037
12038       case EndOfFile:
12039         if (appData.debugMode)
12040           fprintf(debugFP, "Parser hit end of file\n");
12041         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12042           case MT_NONE:
12043           case MT_CHECK:
12044             break;
12045           case MT_CHECKMATE:
12046           case MT_STAINMATE:
12047             if (WhiteOnMove(currentMove)) {
12048                 GameEnds(BlackWins, "Black mates", GE_FILE);
12049             } else {
12050                 GameEnds(WhiteWins, "White mates", GE_FILE);
12051             }
12052             break;
12053           case MT_STALEMATE:
12054             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12055             break;
12056         }
12057         done = TRUE;
12058         break;
12059
12060       case MoveNumberOne:
12061         if (lastLoadGameStart == GNUChessGame) {
12062             /* GNUChessGames have numbers, but they aren't move numbers */
12063             if (appData.debugMode)
12064               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12065                       yy_text, (int) moveType);
12066             return LoadGameOneMove(EndOfFile); /* tail recursion */
12067         }
12068         /* else fall thru */
12069
12070       case XBoardGame:
12071       case GNUChessGame:
12072       case PGNTag:
12073         /* Reached start of next game in file */
12074         if (appData.debugMode)
12075           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12076         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12077           case MT_NONE:
12078           case MT_CHECK:
12079             break;
12080           case MT_CHECKMATE:
12081           case MT_STAINMATE:
12082             if (WhiteOnMove(currentMove)) {
12083                 GameEnds(BlackWins, "Black mates", GE_FILE);
12084             } else {
12085                 GameEnds(WhiteWins, "White mates", GE_FILE);
12086             }
12087             break;
12088           case MT_STALEMATE:
12089             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12090             break;
12091         }
12092         done = TRUE;
12093         break;
12094
12095       case PositionDiagram:     /* should not happen; ignore */
12096       case ElapsedTime:         /* ignore */
12097       case NAG:                 /* ignore */
12098         if (appData.debugMode)
12099           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12100                   yy_text, (int) moveType);
12101         return LoadGameOneMove(EndOfFile); /* tail recursion */
12102
12103       case IllegalMove:
12104         if (appData.testLegality) {
12105             if (appData.debugMode)
12106               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12107             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12108                     (forwardMostMove / 2) + 1,
12109                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12110             DisplayError(move, 0);
12111             done = TRUE;
12112         } else {
12113             if (appData.debugMode)
12114               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12115                       yy_text, currentMoveString);
12116             if(currentMoveString[1] == '@') {
12117                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12118                 fromY = DROP_RANK;
12119             } else {
12120                 fromX = currentMoveString[0] - AAA;
12121                 fromY = currentMoveString[1] - ONE;
12122             }
12123             toX = currentMoveString[2] - AAA;
12124             toY = currentMoveString[3] - ONE;
12125             promoChar = currentMoveString[4];
12126         }
12127         break;
12128
12129       case AmbiguousMove:
12130         if (appData.debugMode)
12131           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12132         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12133                 (forwardMostMove / 2) + 1,
12134                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12135         DisplayError(move, 0);
12136         done = TRUE;
12137         break;
12138
12139       default:
12140       case ImpossibleMove:
12141         if (appData.debugMode)
12142           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12143         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12144                 (forwardMostMove / 2) + 1,
12145                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12146         DisplayError(move, 0);
12147         done = TRUE;
12148         break;
12149     }
12150
12151     if (done) {
12152         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12153             DrawPosition(FALSE, boards[currentMove]);
12154             DisplayBothClocks();
12155             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12156               DisplayComment(currentMove - 1, commentList[currentMove]);
12157         }
12158         (void) StopLoadGameTimer();
12159         gameFileFP = NULL;
12160         cmailOldMove = forwardMostMove;
12161         return FALSE;
12162     } else {
12163         /* currentMoveString is set as a side-effect of yylex */
12164
12165         thinkOutput[0] = NULLCHAR;
12166         MakeMove(fromX, fromY, toX, toY, promoChar);
12167         killX = killY = -1; // [HGM] lion: used up
12168         currentMove = forwardMostMove;
12169         return TRUE;
12170     }
12171 }
12172
12173 /* Load the nth game from the given file */
12174 int
12175 LoadGameFromFile (char *filename, int n, char *title, int useList)
12176 {
12177     FILE *f;
12178     char buf[MSG_SIZ];
12179
12180     if (strcmp(filename, "-") == 0) {
12181         f = stdin;
12182         title = "stdin";
12183     } else {
12184         f = fopen(filename, "rb");
12185         if (f == NULL) {
12186           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12187             DisplayError(buf, errno);
12188             return FALSE;
12189         }
12190     }
12191     if (fseek(f, 0, 0) == -1) {
12192         /* f is not seekable; probably a pipe */
12193         useList = FALSE;
12194     }
12195     if (useList && n == 0) {
12196         int error = GameListBuild(f);
12197         if (error) {
12198             DisplayError(_("Cannot build game list"), error);
12199         } else if (!ListEmpty(&gameList) &&
12200                    ((ListGame *) gameList.tailPred)->number > 1) {
12201             GameListPopUp(f, title);
12202             return TRUE;
12203         }
12204         GameListDestroy();
12205         n = 1;
12206     }
12207     if (n == 0) n = 1;
12208     return LoadGame(f, n, title, FALSE);
12209 }
12210
12211
12212 void
12213 MakeRegisteredMove ()
12214 {
12215     int fromX, fromY, toX, toY;
12216     char promoChar;
12217     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12218         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12219           case CMAIL_MOVE:
12220           case CMAIL_DRAW:
12221             if (appData.debugMode)
12222               fprintf(debugFP, "Restoring %s for game %d\n",
12223                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12224
12225             thinkOutput[0] = NULLCHAR;
12226             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12227             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12228             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12229             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12230             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12231             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12232             MakeMove(fromX, fromY, toX, toY, promoChar);
12233             ShowMove(fromX, fromY, toX, toY);
12234
12235             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12236               case MT_NONE:
12237               case MT_CHECK:
12238                 break;
12239
12240               case MT_CHECKMATE:
12241               case MT_STAINMATE:
12242                 if (WhiteOnMove(currentMove)) {
12243                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12244                 } else {
12245                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12246                 }
12247                 break;
12248
12249               case MT_STALEMATE:
12250                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12251                 break;
12252             }
12253
12254             break;
12255
12256           case CMAIL_RESIGN:
12257             if (WhiteOnMove(currentMove)) {
12258                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12259             } else {
12260                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12261             }
12262             break;
12263
12264           case CMAIL_ACCEPT:
12265             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12266             break;
12267
12268           default:
12269             break;
12270         }
12271     }
12272
12273     return;
12274 }
12275
12276 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12277 int
12278 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12279 {
12280     int retVal;
12281
12282     if (gameNumber > nCmailGames) {
12283         DisplayError(_("No more games in this message"), 0);
12284         return FALSE;
12285     }
12286     if (f == lastLoadGameFP) {
12287         int offset = gameNumber - lastLoadGameNumber;
12288         if (offset == 0) {
12289             cmailMsg[0] = NULLCHAR;
12290             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12291                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12292                 nCmailMovesRegistered--;
12293             }
12294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12295             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12296                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12297             }
12298         } else {
12299             if (! RegisterMove()) return FALSE;
12300         }
12301     }
12302
12303     retVal = LoadGame(f, gameNumber, title, useList);
12304
12305     /* Make move registered during previous look at this game, if any */
12306     MakeRegisteredMove();
12307
12308     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12309         commentList[currentMove]
12310           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12311         DisplayComment(currentMove - 1, commentList[currentMove]);
12312     }
12313
12314     return retVal;
12315 }
12316
12317 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12318 int
12319 ReloadGame (int offset)
12320 {
12321     int gameNumber = lastLoadGameNumber + offset;
12322     if (lastLoadGameFP == NULL) {
12323         DisplayError(_("No game has been loaded yet"), 0);
12324         return FALSE;
12325     }
12326     if (gameNumber <= 0) {
12327         DisplayError(_("Can't back up any further"), 0);
12328         return FALSE;
12329     }
12330     if (cmailMsgLoaded) {
12331         return CmailLoadGame(lastLoadGameFP, gameNumber,
12332                              lastLoadGameTitle, lastLoadGameUseList);
12333     } else {
12334         return LoadGame(lastLoadGameFP, gameNumber,
12335                         lastLoadGameTitle, lastLoadGameUseList);
12336     }
12337 }
12338
12339 int keys[EmptySquare+1];
12340
12341 int
12342 PositionMatches (Board b1, Board b2)
12343 {
12344     int r, f, sum=0;
12345     switch(appData.searchMode) {
12346         case 1: return CompareWithRights(b1, b2);
12347         case 2:
12348             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12349                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12350             }
12351             return TRUE;
12352         case 3:
12353             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12354               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12355                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12356             }
12357             return sum==0;
12358         case 4:
12359             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12360                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12361             }
12362             return sum==0;
12363     }
12364     return TRUE;
12365 }
12366
12367 #define Q_PROMO  4
12368 #define Q_EP     3
12369 #define Q_BCASTL 2
12370 #define Q_WCASTL 1
12371
12372 int pieceList[256], quickBoard[256];
12373 ChessSquare pieceType[256] = { EmptySquare };
12374 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12375 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12376 int soughtTotal, turn;
12377 Boolean epOK, flipSearch;
12378
12379 typedef struct {
12380     unsigned char piece, to;
12381 } Move;
12382
12383 #define DSIZE (250000)
12384
12385 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12386 Move *moveDatabase = initialSpace;
12387 unsigned int movePtr, dataSize = DSIZE;
12388
12389 int
12390 MakePieceList (Board board, int *counts)
12391 {
12392     int r, f, n=Q_PROMO, total=0;
12393     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12394     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12395         int sq = f + (r<<4);
12396         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12397             quickBoard[sq] = ++n;
12398             pieceList[n] = sq;
12399             pieceType[n] = board[r][f];
12400             counts[board[r][f]]++;
12401             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12402             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12403             total++;
12404         }
12405     }
12406     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12407     return total;
12408 }
12409
12410 void
12411 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12412 {
12413     int sq = fromX + (fromY<<4);
12414     int piece = quickBoard[sq], rook;
12415     quickBoard[sq] = 0;
12416     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12417     if(piece == pieceList[1] && fromY == toY) {
12418       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12419         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12420         moveDatabase[movePtr++].piece = Q_WCASTL;
12421         quickBoard[sq] = piece;
12422         piece = quickBoard[from]; quickBoard[from] = 0;
12423         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12424       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12425         quickBoard[sq] = 0; // remove Rook
12426         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12427         moveDatabase[movePtr++].piece = Q_WCASTL;
12428         quickBoard[sq] = pieceList[1]; // put King
12429         piece = rook;
12430         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12431       }
12432     } else
12433     if(piece == pieceList[2] && fromY == toY) {
12434       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12435         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12436         moveDatabase[movePtr++].piece = Q_BCASTL;
12437         quickBoard[sq] = piece;
12438         piece = quickBoard[from]; quickBoard[from] = 0;
12439         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12440       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12441         quickBoard[sq] = 0; // remove Rook
12442         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12443         moveDatabase[movePtr++].piece = Q_BCASTL;
12444         quickBoard[sq] = pieceList[2]; // put King
12445         piece = rook;
12446         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12447       }
12448     } else
12449     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12450         quickBoard[(fromY<<4)+toX] = 0;
12451         moveDatabase[movePtr].piece = Q_EP;
12452         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12453         moveDatabase[movePtr].to = sq;
12454     } else
12455     if(promoPiece != pieceType[piece]) {
12456         moveDatabase[movePtr++].piece = Q_PROMO;
12457         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12458     }
12459     moveDatabase[movePtr].piece = piece;
12460     quickBoard[sq] = piece;
12461     movePtr++;
12462 }
12463
12464 int
12465 PackGame (Board board)
12466 {
12467     Move *newSpace = NULL;
12468     moveDatabase[movePtr].piece = 0; // terminate previous game
12469     if(movePtr > dataSize) {
12470         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12471         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12472         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12473         if(newSpace) {
12474             int i;
12475             Move *p = moveDatabase, *q = newSpace;
12476             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12477             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12478             moveDatabase = newSpace;
12479         } else { // calloc failed, we must be out of memory. Too bad...
12480             dataSize = 0; // prevent calloc events for all subsequent games
12481             return 0;     // and signal this one isn't cached
12482         }
12483     }
12484     movePtr++;
12485     MakePieceList(board, counts);
12486     return movePtr;
12487 }
12488
12489 int
12490 QuickCompare (Board board, int *minCounts, int *maxCounts)
12491 {   // compare according to search mode
12492     int r, f;
12493     switch(appData.searchMode)
12494     {
12495       case 1: // exact position match
12496         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12497         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12498             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12499         }
12500         break;
12501       case 2: // can have extra material on empty squares
12502         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12503             if(board[r][f] == EmptySquare) continue;
12504             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12505         }
12506         break;
12507       case 3: // material with exact Pawn structure
12508         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12509             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12510             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12511         } // fall through to material comparison
12512       case 4: // exact material
12513         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12514         break;
12515       case 6: // material range with given imbalance
12516         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12517         // fall through to range comparison
12518       case 5: // material range
12519         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12520     }
12521     return TRUE;
12522 }
12523
12524 int
12525 QuickScan (Board board, Move *move)
12526 {   // reconstruct game,and compare all positions in it
12527     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12528     do {
12529         int piece = move->piece;
12530         int to = move->to, from = pieceList[piece];
12531         if(found < 0) { // if already found just scan to game end for final piece count
12532           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12533            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12534            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12535                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12536             ) {
12537             static int lastCounts[EmptySquare+1];
12538             int i;
12539             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12540             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12541           } else stretch = 0;
12542           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12543           if(found >= 0 && !appData.minPieces) return found;
12544         }
12545         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12546           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12547           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12548             piece = (++move)->piece;
12549             from = pieceList[piece];
12550             counts[pieceType[piece]]--;
12551             pieceType[piece] = (ChessSquare) move->to;
12552             counts[move->to]++;
12553           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12554             counts[pieceType[quickBoard[to]]]--;
12555             quickBoard[to] = 0; total--;
12556             move++;
12557             continue;
12558           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12559             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12560             from  = pieceList[piece]; // so this must be King
12561             quickBoard[from] = 0;
12562             pieceList[piece] = to;
12563             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12564             quickBoard[from] = 0; // rook
12565             quickBoard[to] = piece;
12566             to = move->to; piece = move->piece;
12567             goto aftercastle;
12568           }
12569         }
12570         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12571         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12572         quickBoard[from] = 0;
12573       aftercastle:
12574         quickBoard[to] = piece;
12575         pieceList[piece] = to;
12576         cnt++; turn ^= 3;
12577         move++;
12578     } while(1);
12579 }
12580
12581 void
12582 InitSearch ()
12583 {
12584     int r, f;
12585     flipSearch = FALSE;
12586     CopyBoard(soughtBoard, boards[currentMove]);
12587     soughtTotal = MakePieceList(soughtBoard, maxSought);
12588     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12589     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12590     CopyBoard(reverseBoard, boards[currentMove]);
12591     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12592         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12593         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12594         reverseBoard[r][f] = piece;
12595     }
12596     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12597     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12598     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12599                  || (boards[currentMove][CASTLING][2] == NoRights ||
12600                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12601                  && (boards[currentMove][CASTLING][5] == NoRights ||
12602                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12603       ) {
12604         flipSearch = TRUE;
12605         CopyBoard(flipBoard, soughtBoard);
12606         CopyBoard(rotateBoard, reverseBoard);
12607         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12608             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12609             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12610         }
12611     }
12612     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12613     if(appData.searchMode >= 5) {
12614         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12615         MakePieceList(soughtBoard, minSought);
12616         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12617     }
12618     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12619         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12620 }
12621
12622 GameInfo dummyInfo;
12623 static int creatingBook;
12624
12625 int
12626 GameContainsPosition (FILE *f, ListGame *lg)
12627 {
12628     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12629     int fromX, fromY, toX, toY;
12630     char promoChar;
12631     static int initDone=FALSE;
12632
12633     // weed out games based on numerical tag comparison
12634     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12635     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12636     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12637     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12638     if(!initDone) {
12639         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12640         initDone = TRUE;
12641     }
12642     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12643     else CopyBoard(boards[scratch], initialPosition); // default start position
12644     if(lg->moves) {
12645         turn = btm + 1;
12646         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12647         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12648     }
12649     if(btm) plyNr++;
12650     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12651     fseek(f, lg->offset, 0);
12652     yynewfile(f);
12653     while(1) {
12654         yyboardindex = scratch;
12655         quickFlag = plyNr+1;
12656         next = Myylex();
12657         quickFlag = 0;
12658         switch(next) {
12659             case PGNTag:
12660                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12661             default:
12662                 continue;
12663
12664             case XBoardGame:
12665             case GNUChessGame:
12666                 if(plyNr) return -1; // after we have seen moves, this is for new game
12667               continue;
12668
12669             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12670             case ImpossibleMove:
12671             case WhiteWins: // game ends here with these four
12672             case BlackWins:
12673             case GameIsDrawn:
12674             case GameUnfinished:
12675                 return -1;
12676
12677             case IllegalMove:
12678                 if(appData.testLegality) return -1;
12679             case WhiteCapturesEnPassant:
12680             case BlackCapturesEnPassant:
12681             case WhitePromotion:
12682             case BlackPromotion:
12683             case WhiteNonPromotion:
12684             case BlackNonPromotion:
12685             case NormalMove:
12686             case FirstLeg:
12687             case WhiteKingSideCastle:
12688             case WhiteQueenSideCastle:
12689             case BlackKingSideCastle:
12690             case BlackQueenSideCastle:
12691             case WhiteKingSideCastleWild:
12692             case WhiteQueenSideCastleWild:
12693             case BlackKingSideCastleWild:
12694             case BlackQueenSideCastleWild:
12695             case WhiteHSideCastleFR:
12696             case WhiteASideCastleFR:
12697             case BlackHSideCastleFR:
12698             case BlackASideCastleFR:
12699                 fromX = currentMoveString[0] - AAA;
12700                 fromY = currentMoveString[1] - ONE;
12701                 toX = currentMoveString[2] - AAA;
12702                 toY = currentMoveString[3] - ONE;
12703                 promoChar = currentMoveString[4];
12704                 break;
12705             case WhiteDrop:
12706             case BlackDrop:
12707                 fromX = next == WhiteDrop ?
12708                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12709                   (int) CharToPiece(ToLower(currentMoveString[0]));
12710                 fromY = DROP_RANK;
12711                 toX = currentMoveString[2] - AAA;
12712                 toY = currentMoveString[3] - ONE;
12713                 promoChar = 0;
12714                 break;
12715         }
12716         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12717         plyNr++;
12718         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12719         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12720         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12721         if(appData.findMirror) {
12722             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12723             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12724         }
12725     }
12726 }
12727
12728 /* Load the nth game from open file f */
12729 int
12730 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12731 {
12732     ChessMove cm;
12733     char buf[MSG_SIZ];
12734     int gn = gameNumber;
12735     ListGame *lg = NULL;
12736     int numPGNTags = 0;
12737     int err, pos = -1;
12738     GameMode oldGameMode;
12739     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12740     char oldName[MSG_SIZ];
12741
12742     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12743
12744     if (appData.debugMode)
12745         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12746
12747     if (gameMode == Training )
12748         SetTrainingModeOff();
12749
12750     oldGameMode = gameMode;
12751     if (gameMode != BeginningOfGame) {
12752       Reset(FALSE, TRUE);
12753     }
12754     killX = killY = -1; // [HGM] lion: in case we did not Reset
12755
12756     gameFileFP = f;
12757     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12758         fclose(lastLoadGameFP);
12759     }
12760
12761     if (useList) {
12762         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12763
12764         if (lg) {
12765             fseek(f, lg->offset, 0);
12766             GameListHighlight(gameNumber);
12767             pos = lg->position;
12768             gn = 1;
12769         }
12770         else {
12771             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12772               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12773             else
12774             DisplayError(_("Game number out of range"), 0);
12775             return FALSE;
12776         }
12777     } else {
12778         GameListDestroy();
12779         if (fseek(f, 0, 0) == -1) {
12780             if (f == lastLoadGameFP ?
12781                 gameNumber == lastLoadGameNumber + 1 :
12782                 gameNumber == 1) {
12783                 gn = 1;
12784             } else {
12785                 DisplayError(_("Can't seek on game file"), 0);
12786                 return FALSE;
12787             }
12788         }
12789     }
12790     lastLoadGameFP = f;
12791     lastLoadGameNumber = gameNumber;
12792     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12793     lastLoadGameUseList = useList;
12794
12795     yynewfile(f);
12796
12797     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12798       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12799                 lg->gameInfo.black);
12800             DisplayTitle(buf);
12801     } else if (*title != NULLCHAR) {
12802         if (gameNumber > 1) {
12803           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12804             DisplayTitle(buf);
12805         } else {
12806             DisplayTitle(title);
12807         }
12808     }
12809
12810     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12811         gameMode = PlayFromGameFile;
12812         ModeHighlight();
12813     }
12814
12815     currentMove = forwardMostMove = backwardMostMove = 0;
12816     CopyBoard(boards[0], initialPosition);
12817     StopClocks();
12818
12819     /*
12820      * Skip the first gn-1 games in the file.
12821      * Also skip over anything that precedes an identifiable
12822      * start of game marker, to avoid being confused by
12823      * garbage at the start of the file.  Currently
12824      * recognized start of game markers are the move number "1",
12825      * the pattern "gnuchess .* game", the pattern
12826      * "^[#;%] [^ ]* game file", and a PGN tag block.
12827      * A game that starts with one of the latter two patterns
12828      * will also have a move number 1, possibly
12829      * following a position diagram.
12830      * 5-4-02: Let's try being more lenient and allowing a game to
12831      * start with an unnumbered move.  Does that break anything?
12832      */
12833     cm = lastLoadGameStart = EndOfFile;
12834     while (gn > 0) {
12835         yyboardindex = forwardMostMove;
12836         cm = (ChessMove) Myylex();
12837         switch (cm) {
12838           case EndOfFile:
12839             if (cmailMsgLoaded) {
12840                 nCmailGames = CMAIL_MAX_GAMES - gn;
12841             } else {
12842                 Reset(TRUE, TRUE);
12843                 DisplayError(_("Game not found in file"), 0);
12844             }
12845             return FALSE;
12846
12847           case GNUChessGame:
12848           case XBoardGame:
12849             gn--;
12850             lastLoadGameStart = cm;
12851             break;
12852
12853           case MoveNumberOne:
12854             switch (lastLoadGameStart) {
12855               case GNUChessGame:
12856               case XBoardGame:
12857               case PGNTag:
12858                 break;
12859               case MoveNumberOne:
12860               case EndOfFile:
12861                 gn--;           /* count this game */
12862                 lastLoadGameStart = cm;
12863                 break;
12864               default:
12865                 /* impossible */
12866                 break;
12867             }
12868             break;
12869
12870           case PGNTag:
12871             switch (lastLoadGameStart) {
12872               case GNUChessGame:
12873               case PGNTag:
12874               case MoveNumberOne:
12875               case EndOfFile:
12876                 gn--;           /* count this game */
12877                 lastLoadGameStart = cm;
12878                 break;
12879               case XBoardGame:
12880                 lastLoadGameStart = cm; /* game counted already */
12881                 break;
12882               default:
12883                 /* impossible */
12884                 break;
12885             }
12886             if (gn > 0) {
12887                 do {
12888                     yyboardindex = forwardMostMove;
12889                     cm = (ChessMove) Myylex();
12890                 } while (cm == PGNTag || cm == Comment);
12891             }
12892             break;
12893
12894           case WhiteWins:
12895           case BlackWins:
12896           case GameIsDrawn:
12897             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12898                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12899                     != CMAIL_OLD_RESULT) {
12900                     nCmailResults ++ ;
12901                     cmailResult[  CMAIL_MAX_GAMES
12902                                 - gn - 1] = CMAIL_OLD_RESULT;
12903                 }
12904             }
12905             break;
12906
12907           case NormalMove:
12908           case FirstLeg:
12909             /* Only a NormalMove can be at the start of a game
12910              * without a position diagram. */
12911             if (lastLoadGameStart == EndOfFile ) {
12912               gn--;
12913               lastLoadGameStart = MoveNumberOne;
12914             }
12915             break;
12916
12917           default:
12918             break;
12919         }
12920     }
12921
12922     if (appData.debugMode)
12923       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12924
12925     if (cm == XBoardGame) {
12926         /* Skip any header junk before position diagram and/or move 1 */
12927         for (;;) {
12928             yyboardindex = forwardMostMove;
12929             cm = (ChessMove) Myylex();
12930
12931             if (cm == EndOfFile ||
12932                 cm == GNUChessGame || cm == XBoardGame) {
12933                 /* Empty game; pretend end-of-file and handle later */
12934                 cm = EndOfFile;
12935                 break;
12936             }
12937
12938             if (cm == MoveNumberOne || cm == PositionDiagram ||
12939                 cm == PGNTag || cm == Comment)
12940               break;
12941         }
12942     } else if (cm == GNUChessGame) {
12943         if (gameInfo.event != NULL) {
12944             free(gameInfo.event);
12945         }
12946         gameInfo.event = StrSave(yy_text);
12947     }
12948
12949     startedFromSetupPosition = FALSE;
12950     while (cm == PGNTag) {
12951         if (appData.debugMode)
12952           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12953         err = ParsePGNTag(yy_text, &gameInfo);
12954         if (!err) numPGNTags++;
12955
12956         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12957         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12958             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12959             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12960             InitPosition(TRUE);
12961             oldVariant = gameInfo.variant;
12962             if (appData.debugMode)
12963               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12964         }
12965
12966
12967         if (gameInfo.fen != NULL) {
12968           Board initial_position;
12969           startedFromSetupPosition = TRUE;
12970           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12971             Reset(TRUE, TRUE);
12972             DisplayError(_("Bad FEN position in file"), 0);
12973             return FALSE;
12974           }
12975           CopyBoard(boards[0], initial_position);
12976           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12977             CopyBoard(initialPosition, initial_position);
12978           if (blackPlaysFirst) {
12979             currentMove = forwardMostMove = backwardMostMove = 1;
12980             CopyBoard(boards[1], initial_position);
12981             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12982             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12983             timeRemaining[0][1] = whiteTimeRemaining;
12984             timeRemaining[1][1] = blackTimeRemaining;
12985             if (commentList[0] != NULL) {
12986               commentList[1] = commentList[0];
12987               commentList[0] = NULL;
12988             }
12989           } else {
12990             currentMove = forwardMostMove = backwardMostMove = 0;
12991           }
12992           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12993           {   int i;
12994               initialRulePlies = FENrulePlies;
12995               for( i=0; i< nrCastlingRights; i++ )
12996                   initialRights[i] = initial_position[CASTLING][i];
12997           }
12998           yyboardindex = forwardMostMove;
12999           free(gameInfo.fen);
13000           gameInfo.fen = NULL;
13001         }
13002
13003         yyboardindex = forwardMostMove;
13004         cm = (ChessMove) Myylex();
13005
13006         /* Handle comments interspersed among the tags */
13007         while (cm == Comment) {
13008             char *p;
13009             if (appData.debugMode)
13010               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13011             p = yy_text;
13012             AppendComment(currentMove, p, FALSE);
13013             yyboardindex = forwardMostMove;
13014             cm = (ChessMove) Myylex();
13015         }
13016     }
13017
13018     /* don't rely on existence of Event tag since if game was
13019      * pasted from clipboard the Event tag may not exist
13020      */
13021     if (numPGNTags > 0){
13022         char *tags;
13023         if (gameInfo.variant == VariantNormal) {
13024           VariantClass v = StringToVariant(gameInfo.event);
13025           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13026           if(v < VariantShogi) gameInfo.variant = v;
13027         }
13028         if (!matchMode) {
13029           if( appData.autoDisplayTags ) {
13030             tags = PGNTags(&gameInfo);
13031             TagsPopUp(tags, CmailMsg());
13032             free(tags);
13033           }
13034         }
13035     } else {
13036         /* Make something up, but don't display it now */
13037         SetGameInfo();
13038         TagsPopDown();
13039     }
13040
13041     if (cm == PositionDiagram) {
13042         int i, j;
13043         char *p;
13044         Board initial_position;
13045
13046         if (appData.debugMode)
13047           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13048
13049         if (!startedFromSetupPosition) {
13050             p = yy_text;
13051             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13052               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13053                 switch (*p) {
13054                   case '{':
13055                   case '[':
13056                   case '-':
13057                   case ' ':
13058                   case '\t':
13059                   case '\n':
13060                   case '\r':
13061                     break;
13062                   default:
13063                     initial_position[i][j++] = CharToPiece(*p);
13064                     break;
13065                 }
13066             while (*p == ' ' || *p == '\t' ||
13067                    *p == '\n' || *p == '\r') p++;
13068
13069             if (strncmp(p, "black", strlen("black"))==0)
13070               blackPlaysFirst = TRUE;
13071             else
13072               blackPlaysFirst = FALSE;
13073             startedFromSetupPosition = TRUE;
13074
13075             CopyBoard(boards[0], initial_position);
13076             if (blackPlaysFirst) {
13077                 currentMove = forwardMostMove = backwardMostMove = 1;
13078                 CopyBoard(boards[1], initial_position);
13079                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13080                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13081                 timeRemaining[0][1] = whiteTimeRemaining;
13082                 timeRemaining[1][1] = blackTimeRemaining;
13083                 if (commentList[0] != NULL) {
13084                     commentList[1] = commentList[0];
13085                     commentList[0] = NULL;
13086                 }
13087             } else {
13088                 currentMove = forwardMostMove = backwardMostMove = 0;
13089             }
13090         }
13091         yyboardindex = forwardMostMove;
13092         cm = (ChessMove) Myylex();
13093     }
13094
13095   if(!creatingBook) {
13096     if (first.pr == NoProc) {
13097         StartChessProgram(&first);
13098     }
13099     InitChessProgram(&first, FALSE);
13100     if(gameInfo.variant == VariantUnknown && *oldName) {
13101         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13102         gameInfo.variant = v;
13103     }
13104     SendToProgram("force\n", &first);
13105     if (startedFromSetupPosition) {
13106         SendBoard(&first, forwardMostMove);
13107     if (appData.debugMode) {
13108         fprintf(debugFP, "Load Game\n");
13109     }
13110         DisplayBothClocks();
13111     }
13112   }
13113
13114     /* [HGM] server: flag to write setup moves in broadcast file as one */
13115     loadFlag = appData.suppressLoadMoves;
13116
13117     while (cm == Comment) {
13118         char *p;
13119         if (appData.debugMode)
13120           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13121         p = yy_text;
13122         AppendComment(currentMove, p, FALSE);
13123         yyboardindex = forwardMostMove;
13124         cm = (ChessMove) Myylex();
13125     }
13126
13127     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13128         cm == WhiteWins || cm == BlackWins ||
13129         cm == GameIsDrawn || cm == GameUnfinished) {
13130         DisplayMessage("", _("No moves in game"));
13131         if (cmailMsgLoaded) {
13132             if (appData.debugMode)
13133               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13134             ClearHighlights();
13135             flipView = FALSE;
13136         }
13137         DrawPosition(FALSE, boards[currentMove]);
13138         DisplayBothClocks();
13139         gameMode = EditGame;
13140         ModeHighlight();
13141         gameFileFP = NULL;
13142         cmailOldMove = 0;
13143         return TRUE;
13144     }
13145
13146     // [HGM] PV info: routine tests if comment empty
13147     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13148         DisplayComment(currentMove - 1, commentList[currentMove]);
13149     }
13150     if (!matchMode && appData.timeDelay != 0)
13151       DrawPosition(FALSE, boards[currentMove]);
13152
13153     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13154       programStats.ok_to_send = 1;
13155     }
13156
13157     /* if the first token after the PGN tags is a move
13158      * and not move number 1, retrieve it from the parser
13159      */
13160     if (cm != MoveNumberOne)
13161         LoadGameOneMove(cm);
13162
13163     /* load the remaining moves from the file */
13164     while (LoadGameOneMove(EndOfFile)) {
13165       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13166       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13167     }
13168
13169     /* rewind to the start of the game */
13170     currentMove = backwardMostMove;
13171
13172     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13173
13174     if (oldGameMode == AnalyzeFile) {
13175       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13176       AnalyzeFileEvent();
13177     } else
13178     if (oldGameMode == AnalyzeMode) {
13179       AnalyzeFileEvent();
13180     }
13181
13182     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13183         long int w, b; // [HGM] adjourn: restore saved clock times
13184         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13185         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13186             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13187             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13188         }
13189     }
13190
13191     if(creatingBook) return TRUE;
13192     if (!matchMode && pos > 0) {
13193         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13194     } else
13195     if (matchMode || appData.timeDelay == 0) {
13196       ToEndEvent();
13197     } else if (appData.timeDelay > 0) {
13198       AutoPlayGameLoop();
13199     }
13200
13201     if (appData.debugMode)
13202         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13203
13204     loadFlag = 0; /* [HGM] true game starts */
13205     return TRUE;
13206 }
13207
13208 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13209 int
13210 ReloadPosition (int offset)
13211 {
13212     int positionNumber = lastLoadPositionNumber + offset;
13213     if (lastLoadPositionFP == NULL) {
13214         DisplayError(_("No position has been loaded yet"), 0);
13215         return FALSE;
13216     }
13217     if (positionNumber <= 0) {
13218         DisplayError(_("Can't back up any further"), 0);
13219         return FALSE;
13220     }
13221     return LoadPosition(lastLoadPositionFP, positionNumber,
13222                         lastLoadPositionTitle);
13223 }
13224
13225 /* Load the nth position from the given file */
13226 int
13227 LoadPositionFromFile (char *filename, int n, char *title)
13228 {
13229     FILE *f;
13230     char buf[MSG_SIZ];
13231
13232     if (strcmp(filename, "-") == 0) {
13233         return LoadPosition(stdin, n, "stdin");
13234     } else {
13235         f = fopen(filename, "rb");
13236         if (f == NULL) {
13237             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13238             DisplayError(buf, errno);
13239             return FALSE;
13240         } else {
13241             return LoadPosition(f, n, title);
13242         }
13243     }
13244 }
13245
13246 /* Load the nth position from the given open file, and close it */
13247 int
13248 LoadPosition (FILE *f, int positionNumber, char *title)
13249 {
13250     char *p, line[MSG_SIZ];
13251     Board initial_position;
13252     int i, j, fenMode, pn;
13253
13254     if (gameMode == Training )
13255         SetTrainingModeOff();
13256
13257     if (gameMode != BeginningOfGame) {
13258         Reset(FALSE, TRUE);
13259     }
13260     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13261         fclose(lastLoadPositionFP);
13262     }
13263     if (positionNumber == 0) positionNumber = 1;
13264     lastLoadPositionFP = f;
13265     lastLoadPositionNumber = positionNumber;
13266     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13267     if (first.pr == NoProc && !appData.noChessProgram) {
13268       StartChessProgram(&first);
13269       InitChessProgram(&first, FALSE);
13270     }
13271     pn = positionNumber;
13272     if (positionNumber < 0) {
13273         /* Negative position number means to seek to that byte offset */
13274         if (fseek(f, -positionNumber, 0) == -1) {
13275             DisplayError(_("Can't seek on position file"), 0);
13276             return FALSE;
13277         };
13278         pn = 1;
13279     } else {
13280         if (fseek(f, 0, 0) == -1) {
13281             if (f == lastLoadPositionFP ?
13282                 positionNumber == lastLoadPositionNumber + 1 :
13283                 positionNumber == 1) {
13284                 pn = 1;
13285             } else {
13286                 DisplayError(_("Can't seek on position file"), 0);
13287                 return FALSE;
13288             }
13289         }
13290     }
13291     /* See if this file is FEN or old-style xboard */
13292     if (fgets(line, MSG_SIZ, f) == NULL) {
13293         DisplayError(_("Position not found in file"), 0);
13294         return FALSE;
13295     }
13296     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13297     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13298
13299     if (pn >= 2) {
13300         if (fenMode || line[0] == '#') pn--;
13301         while (pn > 0) {
13302             /* skip positions before number pn */
13303             if (fgets(line, MSG_SIZ, f) == NULL) {
13304                 Reset(TRUE, TRUE);
13305                 DisplayError(_("Position not found in file"), 0);
13306                 return FALSE;
13307             }
13308             if (fenMode || line[0] == '#') pn--;
13309         }
13310     }
13311
13312     if (fenMode) {
13313         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13314             DisplayError(_("Bad FEN position in file"), 0);
13315             return FALSE;
13316         }
13317     } else {
13318         (void) fgets(line, MSG_SIZ, f);
13319         (void) fgets(line, MSG_SIZ, f);
13320
13321         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13322             (void) fgets(line, MSG_SIZ, f);
13323             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13324                 if (*p == ' ')
13325                   continue;
13326                 initial_position[i][j++] = CharToPiece(*p);
13327             }
13328         }
13329
13330         blackPlaysFirst = FALSE;
13331         if (!feof(f)) {
13332             (void) fgets(line, MSG_SIZ, f);
13333             if (strncmp(line, "black", strlen("black"))==0)
13334               blackPlaysFirst = TRUE;
13335         }
13336     }
13337     startedFromSetupPosition = TRUE;
13338
13339     CopyBoard(boards[0], initial_position);
13340     if (blackPlaysFirst) {
13341         currentMove = forwardMostMove = backwardMostMove = 1;
13342         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13343         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13344         CopyBoard(boards[1], initial_position);
13345         DisplayMessage("", _("Black to play"));
13346     } else {
13347         currentMove = forwardMostMove = backwardMostMove = 0;
13348         DisplayMessage("", _("White to play"));
13349     }
13350     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13351     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13352         SendToProgram("force\n", &first);
13353         SendBoard(&first, forwardMostMove);
13354     }
13355     if (appData.debugMode) {
13356 int i, j;
13357   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13358   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13359         fprintf(debugFP, "Load Position\n");
13360     }
13361
13362     if (positionNumber > 1) {
13363       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13364         DisplayTitle(line);
13365     } else {
13366         DisplayTitle(title);
13367     }
13368     gameMode = EditGame;
13369     ModeHighlight();
13370     ResetClocks();
13371     timeRemaining[0][1] = whiteTimeRemaining;
13372     timeRemaining[1][1] = blackTimeRemaining;
13373     DrawPosition(FALSE, boards[currentMove]);
13374
13375     return TRUE;
13376 }
13377
13378
13379 void
13380 CopyPlayerNameIntoFileName (char **dest, char *src)
13381 {
13382     while (*src != NULLCHAR && *src != ',') {
13383         if (*src == ' ') {
13384             *(*dest)++ = '_';
13385             src++;
13386         } else {
13387             *(*dest)++ = *src++;
13388         }
13389     }
13390 }
13391
13392 char *
13393 DefaultFileName (char *ext)
13394 {
13395     static char def[MSG_SIZ];
13396     char *p;
13397
13398     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13399         p = def;
13400         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13401         *p++ = '-';
13402         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13403         *p++ = '.';
13404         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13405     } else {
13406         def[0] = NULLCHAR;
13407     }
13408     return def;
13409 }
13410
13411 /* Save the current game to the given file */
13412 int
13413 SaveGameToFile (char *filename, int append)
13414 {
13415     FILE *f;
13416     char buf[MSG_SIZ];
13417     int result, i, t,tot=0;
13418
13419     if (strcmp(filename, "-") == 0) {
13420         return SaveGame(stdout, 0, NULL);
13421     } else {
13422         for(i=0; i<10; i++) { // upto 10 tries
13423              f = fopen(filename, append ? "a" : "w");
13424              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13425              if(f || errno != 13) break;
13426              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13427              tot += t;
13428         }
13429         if (f == NULL) {
13430             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13431             DisplayError(buf, errno);
13432             return FALSE;
13433         } else {
13434             safeStrCpy(buf, lastMsg, MSG_SIZ);
13435             DisplayMessage(_("Waiting for access to save file"), "");
13436             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13437             DisplayMessage(_("Saving game"), "");
13438             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13439             result = SaveGame(f, 0, NULL);
13440             DisplayMessage(buf, "");
13441             return result;
13442         }
13443     }
13444 }
13445
13446 char *
13447 SavePart (char *str)
13448 {
13449     static char buf[MSG_SIZ];
13450     char *p;
13451
13452     p = strchr(str, ' ');
13453     if (p == NULL) return str;
13454     strncpy(buf, str, p - str);
13455     buf[p - str] = NULLCHAR;
13456     return buf;
13457 }
13458
13459 #define PGN_MAX_LINE 75
13460
13461 #define PGN_SIDE_WHITE  0
13462 #define PGN_SIDE_BLACK  1
13463
13464 static int
13465 FindFirstMoveOutOfBook (int side)
13466 {
13467     int result = -1;
13468
13469     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13470         int index = backwardMostMove;
13471         int has_book_hit = 0;
13472
13473         if( (index % 2) != side ) {
13474             index++;
13475         }
13476
13477         while( index < forwardMostMove ) {
13478             /* Check to see if engine is in book */
13479             int depth = pvInfoList[index].depth;
13480             int score = pvInfoList[index].score;
13481             int in_book = 0;
13482
13483             if( depth <= 2 ) {
13484                 in_book = 1;
13485             }
13486             else if( score == 0 && depth == 63 ) {
13487                 in_book = 1; /* Zappa */
13488             }
13489             else if( score == 2 && depth == 99 ) {
13490                 in_book = 1; /* Abrok */
13491             }
13492
13493             has_book_hit += in_book;
13494
13495             if( ! in_book ) {
13496                 result = index;
13497
13498                 break;
13499             }
13500
13501             index += 2;
13502         }
13503     }
13504
13505     return result;
13506 }
13507
13508 void
13509 GetOutOfBookInfo (char * buf)
13510 {
13511     int oob[2];
13512     int i;
13513     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13514
13515     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13516     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13517
13518     *buf = '\0';
13519
13520     if( oob[0] >= 0 || oob[1] >= 0 ) {
13521         for( i=0; i<2; i++ ) {
13522             int idx = oob[i];
13523
13524             if( idx >= 0 ) {
13525                 if( i > 0 && oob[0] >= 0 ) {
13526                     strcat( buf, "   " );
13527                 }
13528
13529                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13530                 sprintf( buf+strlen(buf), "%s%.2f",
13531                     pvInfoList[idx].score >= 0 ? "+" : "",
13532                     pvInfoList[idx].score / 100.0 );
13533             }
13534         }
13535     }
13536 }
13537
13538 /* Save game in PGN style */
13539 static void
13540 SaveGamePGN2 (FILE *f)
13541 {
13542     int i, offset, linelen, newblock;
13543 //    char *movetext;
13544     char numtext[32];
13545     int movelen, numlen, blank;
13546     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13547
13548     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13549
13550     PrintPGNTags(f, &gameInfo);
13551
13552     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13553
13554     if (backwardMostMove > 0 || startedFromSetupPosition) {
13555         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13556         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13557         fprintf(f, "\n{--------------\n");
13558         PrintPosition(f, backwardMostMove);
13559         fprintf(f, "--------------}\n");
13560         free(fen);
13561     }
13562     else {
13563         /* [AS] Out of book annotation */
13564         if( appData.saveOutOfBookInfo ) {
13565             char buf[64];
13566
13567             GetOutOfBookInfo( buf );
13568
13569             if( buf[0] != '\0' ) {
13570                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13571             }
13572         }
13573
13574         fprintf(f, "\n");
13575     }
13576
13577     i = backwardMostMove;
13578     linelen = 0;
13579     newblock = TRUE;
13580
13581     while (i < forwardMostMove) {
13582         /* Print comments preceding this move */
13583         if (commentList[i] != NULL) {
13584             if (linelen > 0) fprintf(f, "\n");
13585             fprintf(f, "%s", commentList[i]);
13586             linelen = 0;
13587             newblock = TRUE;
13588         }
13589
13590         /* Format move number */
13591         if ((i % 2) == 0)
13592           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13593         else
13594           if (newblock)
13595             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13596           else
13597             numtext[0] = NULLCHAR;
13598
13599         numlen = strlen(numtext);
13600         newblock = FALSE;
13601
13602         /* Print move number */
13603         blank = linelen > 0 && numlen > 0;
13604         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13605             fprintf(f, "\n");
13606             linelen = 0;
13607             blank = 0;
13608         }
13609         if (blank) {
13610             fprintf(f, " ");
13611             linelen++;
13612         }
13613         fprintf(f, "%s", numtext);
13614         linelen += numlen;
13615
13616         /* Get move */
13617         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13618         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13619
13620         /* Print move */
13621         blank = linelen > 0 && movelen > 0;
13622         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13623             fprintf(f, "\n");
13624             linelen = 0;
13625             blank = 0;
13626         }
13627         if (blank) {
13628             fprintf(f, " ");
13629             linelen++;
13630         }
13631         fprintf(f, "%s", move_buffer);
13632         linelen += movelen;
13633
13634         /* [AS] Add PV info if present */
13635         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13636             /* [HGM] add time */
13637             char buf[MSG_SIZ]; int seconds;
13638
13639             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13640
13641             if( seconds <= 0)
13642               buf[0] = 0;
13643             else
13644               if( seconds < 30 )
13645                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13646               else
13647                 {
13648                   seconds = (seconds + 4)/10; // round to full seconds
13649                   if( seconds < 60 )
13650                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13651                   else
13652                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13653                 }
13654
13655             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13656                       pvInfoList[i].score >= 0 ? "+" : "",
13657                       pvInfoList[i].score / 100.0,
13658                       pvInfoList[i].depth,
13659                       buf );
13660
13661             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13662
13663             /* Print score/depth */
13664             blank = linelen > 0 && movelen > 0;
13665             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13666                 fprintf(f, "\n");
13667                 linelen = 0;
13668                 blank = 0;
13669             }
13670             if (blank) {
13671                 fprintf(f, " ");
13672                 linelen++;
13673             }
13674             fprintf(f, "%s", move_buffer);
13675             linelen += movelen;
13676         }
13677
13678         i++;
13679     }
13680
13681     /* Start a new line */
13682     if (linelen > 0) fprintf(f, "\n");
13683
13684     /* Print comments after last move */
13685     if (commentList[i] != NULL) {
13686         fprintf(f, "%s\n", commentList[i]);
13687     }
13688
13689     /* Print result */
13690     if (gameInfo.resultDetails != NULL &&
13691         gameInfo.resultDetails[0] != NULLCHAR) {
13692         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13693         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13694            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13695             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13696         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13697     } else {
13698         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13699     }
13700 }
13701
13702 /* Save game in PGN style and close the file */
13703 int
13704 SaveGamePGN (FILE *f)
13705 {
13706     SaveGamePGN2(f);
13707     fclose(f);
13708     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13709     return TRUE;
13710 }
13711
13712 /* Save game in old style and close the file */
13713 int
13714 SaveGameOldStyle (FILE *f)
13715 {
13716     int i, offset;
13717     time_t tm;
13718
13719     tm = time((time_t *) NULL);
13720
13721     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13722     PrintOpponents(f);
13723
13724     if (backwardMostMove > 0 || startedFromSetupPosition) {
13725         fprintf(f, "\n[--------------\n");
13726         PrintPosition(f, backwardMostMove);
13727         fprintf(f, "--------------]\n");
13728     } else {
13729         fprintf(f, "\n");
13730     }
13731
13732     i = backwardMostMove;
13733     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13734
13735     while (i < forwardMostMove) {
13736         if (commentList[i] != NULL) {
13737             fprintf(f, "[%s]\n", commentList[i]);
13738         }
13739
13740         if ((i % 2) == 1) {
13741             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13742             i++;
13743         } else {
13744             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13745             i++;
13746             if (commentList[i] != NULL) {
13747                 fprintf(f, "\n");
13748                 continue;
13749             }
13750             if (i >= forwardMostMove) {
13751                 fprintf(f, "\n");
13752                 break;
13753             }
13754             fprintf(f, "%s\n", parseList[i]);
13755             i++;
13756         }
13757     }
13758
13759     if (commentList[i] != NULL) {
13760         fprintf(f, "[%s]\n", commentList[i]);
13761     }
13762
13763     /* This isn't really the old style, but it's close enough */
13764     if (gameInfo.resultDetails != NULL &&
13765         gameInfo.resultDetails[0] != NULLCHAR) {
13766         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13767                 gameInfo.resultDetails);
13768     } else {
13769         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13770     }
13771
13772     fclose(f);
13773     return TRUE;
13774 }
13775
13776 /* Save the current game to open file f and close the file */
13777 int
13778 SaveGame (FILE *f, int dummy, char *dummy2)
13779 {
13780     if (gameMode == EditPosition) EditPositionDone(TRUE);
13781     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13782     if (appData.oldSaveStyle)
13783       return SaveGameOldStyle(f);
13784     else
13785       return SaveGamePGN(f);
13786 }
13787
13788 /* Save the current position to the given file */
13789 int
13790 SavePositionToFile (char *filename)
13791 {
13792     FILE *f;
13793     char buf[MSG_SIZ];
13794
13795     if (strcmp(filename, "-") == 0) {
13796         return SavePosition(stdout, 0, NULL);
13797     } else {
13798         f = fopen(filename, "a");
13799         if (f == NULL) {
13800             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13801             DisplayError(buf, errno);
13802             return FALSE;
13803         } else {
13804             safeStrCpy(buf, lastMsg, MSG_SIZ);
13805             DisplayMessage(_("Waiting for access to save file"), "");
13806             flock(fileno(f), LOCK_EX); // [HGM] lock
13807             DisplayMessage(_("Saving position"), "");
13808             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13809             SavePosition(f, 0, NULL);
13810             DisplayMessage(buf, "");
13811             return TRUE;
13812         }
13813     }
13814 }
13815
13816 /* Save the current position to the given open file and close the file */
13817 int
13818 SavePosition (FILE *f, int dummy, char *dummy2)
13819 {
13820     time_t tm;
13821     char *fen;
13822
13823     if (gameMode == EditPosition) EditPositionDone(TRUE);
13824     if (appData.oldSaveStyle) {
13825         tm = time((time_t *) NULL);
13826
13827         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13828         PrintOpponents(f);
13829         fprintf(f, "[--------------\n");
13830         PrintPosition(f, currentMove);
13831         fprintf(f, "--------------]\n");
13832     } else {
13833         fen = PositionToFEN(currentMove, NULL, 1);
13834         fprintf(f, "%s\n", fen);
13835         free(fen);
13836     }
13837     fclose(f);
13838     return TRUE;
13839 }
13840
13841 void
13842 ReloadCmailMsgEvent (int unregister)
13843 {
13844 #if !WIN32
13845     static char *inFilename = NULL;
13846     static char *outFilename;
13847     int i;
13848     struct stat inbuf, outbuf;
13849     int status;
13850
13851     /* Any registered moves are unregistered if unregister is set, */
13852     /* i.e. invoked by the signal handler */
13853     if (unregister) {
13854         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13855             cmailMoveRegistered[i] = FALSE;
13856             if (cmailCommentList[i] != NULL) {
13857                 free(cmailCommentList[i]);
13858                 cmailCommentList[i] = NULL;
13859             }
13860         }
13861         nCmailMovesRegistered = 0;
13862     }
13863
13864     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13865         cmailResult[i] = CMAIL_NOT_RESULT;
13866     }
13867     nCmailResults = 0;
13868
13869     if (inFilename == NULL) {
13870         /* Because the filenames are static they only get malloced once  */
13871         /* and they never get freed                                      */
13872         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13873         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13874
13875         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13876         sprintf(outFilename, "%s.out", appData.cmailGameName);
13877     }
13878
13879     status = stat(outFilename, &outbuf);
13880     if (status < 0) {
13881         cmailMailedMove = FALSE;
13882     } else {
13883         status = stat(inFilename, &inbuf);
13884         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13885     }
13886
13887     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13888        counts the games, notes how each one terminated, etc.
13889
13890        It would be nice to remove this kludge and instead gather all
13891        the information while building the game list.  (And to keep it
13892        in the game list nodes instead of having a bunch of fixed-size
13893        parallel arrays.)  Note this will require getting each game's
13894        termination from the PGN tags, as the game list builder does
13895        not process the game moves.  --mann
13896        */
13897     cmailMsgLoaded = TRUE;
13898     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13899
13900     /* Load first game in the file or popup game menu */
13901     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13902
13903 #endif /* !WIN32 */
13904     return;
13905 }
13906
13907 int
13908 RegisterMove ()
13909 {
13910     FILE *f;
13911     char string[MSG_SIZ];
13912
13913     if (   cmailMailedMove
13914         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13915         return TRUE;            /* Allow free viewing  */
13916     }
13917
13918     /* Unregister move to ensure that we don't leave RegisterMove        */
13919     /* with the move registered when the conditions for registering no   */
13920     /* longer hold                                                       */
13921     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13922         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13923         nCmailMovesRegistered --;
13924
13925         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13926           {
13927               free(cmailCommentList[lastLoadGameNumber - 1]);
13928               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13929           }
13930     }
13931
13932     if (cmailOldMove == -1) {
13933         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13934         return FALSE;
13935     }
13936
13937     if (currentMove > cmailOldMove + 1) {
13938         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13939         return FALSE;
13940     }
13941
13942     if (currentMove < cmailOldMove) {
13943         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13944         return FALSE;
13945     }
13946
13947     if (forwardMostMove > currentMove) {
13948         /* Silently truncate extra moves */
13949         TruncateGame();
13950     }
13951
13952     if (   (currentMove == cmailOldMove + 1)
13953         || (   (currentMove == cmailOldMove)
13954             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13955                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13956         if (gameInfo.result != GameUnfinished) {
13957             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13958         }
13959
13960         if (commentList[currentMove] != NULL) {
13961             cmailCommentList[lastLoadGameNumber - 1]
13962               = StrSave(commentList[currentMove]);
13963         }
13964         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13965
13966         if (appData.debugMode)
13967           fprintf(debugFP, "Saving %s for game %d\n",
13968                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13969
13970         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13971
13972         f = fopen(string, "w");
13973         if (appData.oldSaveStyle) {
13974             SaveGameOldStyle(f); /* also closes the file */
13975
13976             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13977             f = fopen(string, "w");
13978             SavePosition(f, 0, NULL); /* also closes the file */
13979         } else {
13980             fprintf(f, "{--------------\n");
13981             PrintPosition(f, currentMove);
13982             fprintf(f, "--------------}\n\n");
13983
13984             SaveGame(f, 0, NULL); /* also closes the file*/
13985         }
13986
13987         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13988         nCmailMovesRegistered ++;
13989     } else if (nCmailGames == 1) {
13990         DisplayError(_("You have not made a move yet"), 0);
13991         return FALSE;
13992     }
13993
13994     return TRUE;
13995 }
13996
13997 void
13998 MailMoveEvent ()
13999 {
14000 #if !WIN32
14001     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14002     FILE *commandOutput;
14003     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14004     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14005     int nBuffers;
14006     int i;
14007     int archived;
14008     char *arcDir;
14009
14010     if (! cmailMsgLoaded) {
14011         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14012         return;
14013     }
14014
14015     if (nCmailGames == nCmailResults) {
14016         DisplayError(_("No unfinished games"), 0);
14017         return;
14018     }
14019
14020 #if CMAIL_PROHIBIT_REMAIL
14021     if (cmailMailedMove) {
14022       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);
14023         DisplayError(msg, 0);
14024         return;
14025     }
14026 #endif
14027
14028     if (! (cmailMailedMove || RegisterMove())) return;
14029
14030     if (   cmailMailedMove
14031         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14032       snprintf(string, MSG_SIZ, partCommandString,
14033                appData.debugMode ? " -v" : "", appData.cmailGameName);
14034         commandOutput = popen(string, "r");
14035
14036         if (commandOutput == NULL) {
14037             DisplayError(_("Failed to invoke cmail"), 0);
14038         } else {
14039             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14040                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14041             }
14042             if (nBuffers > 1) {
14043                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14044                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14045                 nBytes = MSG_SIZ - 1;
14046             } else {
14047                 (void) memcpy(msg, buffer, nBytes);
14048             }
14049             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14050
14051             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14052                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14053
14054                 archived = TRUE;
14055                 for (i = 0; i < nCmailGames; i ++) {
14056                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14057                         archived = FALSE;
14058                     }
14059                 }
14060                 if (   archived
14061                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14062                         != NULL)) {
14063                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14064                            arcDir,
14065                            appData.cmailGameName,
14066                            gameInfo.date);
14067                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14068                     cmailMsgLoaded = FALSE;
14069                 }
14070             }
14071
14072             DisplayInformation(msg);
14073             pclose(commandOutput);
14074         }
14075     } else {
14076         if ((*cmailMsg) != '\0') {
14077             DisplayInformation(cmailMsg);
14078         }
14079     }
14080
14081     return;
14082 #endif /* !WIN32 */
14083 }
14084
14085 char *
14086 CmailMsg ()
14087 {
14088 #if WIN32
14089     return NULL;
14090 #else
14091     int  prependComma = 0;
14092     char number[5];
14093     char string[MSG_SIZ];       /* Space for game-list */
14094     int  i;
14095
14096     if (!cmailMsgLoaded) return "";
14097
14098     if (cmailMailedMove) {
14099       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14100     } else {
14101         /* Create a list of games left */
14102       snprintf(string, MSG_SIZ, "[");
14103         for (i = 0; i < nCmailGames; i ++) {
14104             if (! (   cmailMoveRegistered[i]
14105                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14106                 if (prependComma) {
14107                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14108                 } else {
14109                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14110                     prependComma = 1;
14111                 }
14112
14113                 strcat(string, number);
14114             }
14115         }
14116         strcat(string, "]");
14117
14118         if (nCmailMovesRegistered + nCmailResults == 0) {
14119             switch (nCmailGames) {
14120               case 1:
14121                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14122                 break;
14123
14124               case 2:
14125                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14126                 break;
14127
14128               default:
14129                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14130                          nCmailGames);
14131                 break;
14132             }
14133         } else {
14134             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14135               case 1:
14136                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14137                          string);
14138                 break;
14139
14140               case 0:
14141                 if (nCmailResults == nCmailGames) {
14142                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14143                 } else {
14144                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14145                 }
14146                 break;
14147
14148               default:
14149                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14150                          string);
14151             }
14152         }
14153     }
14154     return cmailMsg;
14155 #endif /* WIN32 */
14156 }
14157
14158 void
14159 ResetGameEvent ()
14160 {
14161     if (gameMode == Training)
14162       SetTrainingModeOff();
14163
14164     Reset(TRUE, TRUE);
14165     cmailMsgLoaded = FALSE;
14166     if (appData.icsActive) {
14167       SendToICS(ics_prefix);
14168       SendToICS("refresh\n");
14169     }
14170 }
14171
14172 void
14173 ExitEvent (int status)
14174 {
14175     exiting++;
14176     if (exiting > 2) {
14177       /* Give up on clean exit */
14178       exit(status);
14179     }
14180     if (exiting > 1) {
14181       /* Keep trying for clean exit */
14182       return;
14183     }
14184
14185     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14186     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14187
14188     if (telnetISR != NULL) {
14189       RemoveInputSource(telnetISR);
14190     }
14191     if (icsPR != NoProc) {
14192       DestroyChildProcess(icsPR, TRUE);
14193     }
14194
14195     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14196     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14197
14198     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14199     /* make sure this other one finishes before killing it!                  */
14200     if(endingGame) { int count = 0;
14201         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14202         while(endingGame && count++ < 10) DoSleep(1);
14203         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14204     }
14205
14206     /* Kill off chess programs */
14207     if (first.pr != NoProc) {
14208         ExitAnalyzeMode();
14209
14210         DoSleep( appData.delayBeforeQuit );
14211         SendToProgram("quit\n", &first);
14212         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14213     }
14214     if (second.pr != NoProc) {
14215         DoSleep( appData.delayBeforeQuit );
14216         SendToProgram("quit\n", &second);
14217         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14218     }
14219     if (first.isr != NULL) {
14220         RemoveInputSource(first.isr);
14221     }
14222     if (second.isr != NULL) {
14223         RemoveInputSource(second.isr);
14224     }
14225
14226     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14227     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14228
14229     ShutDownFrontEnd();
14230     exit(status);
14231 }
14232
14233 void
14234 PauseEngine (ChessProgramState *cps)
14235 {
14236     SendToProgram("pause\n", cps);
14237     cps->pause = 2;
14238 }
14239
14240 void
14241 UnPauseEngine (ChessProgramState *cps)
14242 {
14243     SendToProgram("resume\n", cps);
14244     cps->pause = 1;
14245 }
14246
14247 void
14248 PauseEvent ()
14249 {
14250     if (appData.debugMode)
14251         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14252     if (pausing) {
14253         pausing = FALSE;
14254         ModeHighlight();
14255         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14256             StartClocks();
14257             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14258                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14259                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14260             }
14261             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14262             HandleMachineMove(stashedInputMove, stalledEngine);
14263             stalledEngine = NULL;
14264             return;
14265         }
14266         if (gameMode == MachinePlaysWhite ||
14267             gameMode == TwoMachinesPlay   ||
14268             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14269             if(first.pause)  UnPauseEngine(&first);
14270             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14271             if(second.pause) UnPauseEngine(&second);
14272             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14273             StartClocks();
14274         } else {
14275             DisplayBothClocks();
14276         }
14277         if (gameMode == PlayFromGameFile) {
14278             if (appData.timeDelay >= 0)
14279                 AutoPlayGameLoop();
14280         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14281             Reset(FALSE, TRUE);
14282             SendToICS(ics_prefix);
14283             SendToICS("refresh\n");
14284         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14285             ForwardInner(forwardMostMove);
14286         }
14287         pauseExamInvalid = FALSE;
14288     } else {
14289         switch (gameMode) {
14290           default:
14291             return;
14292           case IcsExamining:
14293             pauseExamForwardMostMove = forwardMostMove;
14294             pauseExamInvalid = FALSE;
14295             /* fall through */
14296           case IcsObserving:
14297           case IcsPlayingWhite:
14298           case IcsPlayingBlack:
14299             pausing = TRUE;
14300             ModeHighlight();
14301             return;
14302           case PlayFromGameFile:
14303             (void) StopLoadGameTimer();
14304             pausing = TRUE;
14305             ModeHighlight();
14306             break;
14307           case BeginningOfGame:
14308             if (appData.icsActive) return;
14309             /* else fall through */
14310           case MachinePlaysWhite:
14311           case MachinePlaysBlack:
14312           case TwoMachinesPlay:
14313             if (forwardMostMove == 0)
14314               return;           /* don't pause if no one has moved */
14315             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14316                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14317                 if(onMove->pause) {           // thinking engine can be paused
14318                     PauseEngine(onMove);      // do it
14319                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14320                         PauseEngine(onMove->other);
14321                     else
14322                         SendToProgram("easy\n", onMove->other);
14323                     StopClocks();
14324                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14325             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14326                 if(first.pause) {
14327                     PauseEngine(&first);
14328                     StopClocks();
14329                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14330             } else { // human on move, pause pondering by either method
14331                 if(first.pause)
14332                     PauseEngine(&first);
14333                 else if(appData.ponderNextMove)
14334                     SendToProgram("easy\n", &first);
14335                 StopClocks();
14336             }
14337             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14338           case AnalyzeMode:
14339             pausing = TRUE;
14340             ModeHighlight();
14341             break;
14342         }
14343     }
14344 }
14345
14346 void
14347 EditCommentEvent ()
14348 {
14349     char title[MSG_SIZ];
14350
14351     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14352       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14353     } else {
14354       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14355                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14356                parseList[currentMove - 1]);
14357     }
14358
14359     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14360 }
14361
14362
14363 void
14364 EditTagsEvent ()
14365 {
14366     char *tags = PGNTags(&gameInfo);
14367     bookUp = FALSE;
14368     EditTagsPopUp(tags, NULL);
14369     free(tags);
14370 }
14371
14372 void
14373 ToggleSecond ()
14374 {
14375   if(second.analyzing) {
14376     SendToProgram("exit\n", &second);
14377     second.analyzing = FALSE;
14378   } else {
14379     if (second.pr == NoProc) StartChessProgram(&second);
14380     InitChessProgram(&second, FALSE);
14381     FeedMovesToProgram(&second, currentMove);
14382
14383     SendToProgram("analyze\n", &second);
14384     second.analyzing = TRUE;
14385   }
14386 }
14387
14388 /* Toggle ShowThinking */
14389 void
14390 ToggleShowThinking()
14391 {
14392   appData.showThinking = !appData.showThinking;
14393   ShowThinkingEvent();
14394 }
14395
14396 int
14397 AnalyzeModeEvent ()
14398 {
14399     char buf[MSG_SIZ];
14400
14401     if (!first.analysisSupport) {
14402       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14403       DisplayError(buf, 0);
14404       return 0;
14405     }
14406     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14407     if (appData.icsActive) {
14408         if (gameMode != IcsObserving) {
14409           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14410             DisplayError(buf, 0);
14411             /* secure check */
14412             if (appData.icsEngineAnalyze) {
14413                 if (appData.debugMode)
14414                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14415                 ExitAnalyzeMode();
14416                 ModeHighlight();
14417             }
14418             return 0;
14419         }
14420         /* if enable, user wants to disable icsEngineAnalyze */
14421         if (appData.icsEngineAnalyze) {
14422                 ExitAnalyzeMode();
14423                 ModeHighlight();
14424                 return 0;
14425         }
14426         appData.icsEngineAnalyze = TRUE;
14427         if (appData.debugMode)
14428             fprintf(debugFP, "ICS engine analyze starting... \n");
14429     }
14430
14431     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14432     if (appData.noChessProgram || gameMode == AnalyzeMode)
14433       return 0;
14434
14435     if (gameMode != AnalyzeFile) {
14436         if (!appData.icsEngineAnalyze) {
14437                EditGameEvent();
14438                if (gameMode != EditGame) return 0;
14439         }
14440         if (!appData.showThinking) ToggleShowThinking();
14441         ResurrectChessProgram();
14442         SendToProgram("analyze\n", &first);
14443         first.analyzing = TRUE;
14444         /*first.maybeThinking = TRUE;*/
14445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14446         EngineOutputPopUp();
14447     }
14448     if (!appData.icsEngineAnalyze) {
14449         gameMode = AnalyzeMode;
14450         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14451     }
14452     pausing = FALSE;
14453     ModeHighlight();
14454     SetGameInfo();
14455
14456     StartAnalysisClock();
14457     GetTimeMark(&lastNodeCountTime);
14458     lastNodeCount = 0;
14459     return 1;
14460 }
14461
14462 void
14463 AnalyzeFileEvent ()
14464 {
14465     if (appData.noChessProgram || gameMode == AnalyzeFile)
14466       return;
14467
14468     if (!first.analysisSupport) {
14469       char buf[MSG_SIZ];
14470       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14471       DisplayError(buf, 0);
14472       return;
14473     }
14474
14475     if (gameMode != AnalyzeMode) {
14476         keepInfo = 1; // mere annotating should not alter PGN tags
14477         EditGameEvent();
14478         keepInfo = 0;
14479         if (gameMode != EditGame) return;
14480         if (!appData.showThinking) ToggleShowThinking();
14481         ResurrectChessProgram();
14482         SendToProgram("analyze\n", &first);
14483         first.analyzing = TRUE;
14484         /*first.maybeThinking = TRUE;*/
14485         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14486         EngineOutputPopUp();
14487     }
14488     gameMode = AnalyzeFile;
14489     pausing = FALSE;
14490     ModeHighlight();
14491
14492     StartAnalysisClock();
14493     GetTimeMark(&lastNodeCountTime);
14494     lastNodeCount = 0;
14495     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14496     AnalysisPeriodicEvent(1);
14497 }
14498
14499 void
14500 MachineWhiteEvent ()
14501 {
14502     char buf[MSG_SIZ];
14503     char *bookHit = NULL;
14504
14505     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14506       return;
14507
14508
14509     if (gameMode == PlayFromGameFile ||
14510         gameMode == TwoMachinesPlay  ||
14511         gameMode == Training         ||
14512         gameMode == AnalyzeMode      ||
14513         gameMode == EndOfGame)
14514         EditGameEvent();
14515
14516     if (gameMode == EditPosition)
14517         EditPositionDone(TRUE);
14518
14519     if (!WhiteOnMove(currentMove)) {
14520         DisplayError(_("It is not White's turn"), 0);
14521         return;
14522     }
14523
14524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14525       ExitAnalyzeMode();
14526
14527     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14528         gameMode == AnalyzeFile)
14529         TruncateGame();
14530
14531     ResurrectChessProgram();    /* in case it isn't running */
14532     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14533         gameMode = MachinePlaysWhite;
14534         ResetClocks();
14535     } else
14536     gameMode = MachinePlaysWhite;
14537     pausing = FALSE;
14538     ModeHighlight();
14539     SetGameInfo();
14540     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14541     DisplayTitle(buf);
14542     if (first.sendName) {
14543       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14544       SendToProgram(buf, &first);
14545     }
14546     if (first.sendTime) {
14547       if (first.useColors) {
14548         SendToProgram("black\n", &first); /*gnu kludge*/
14549       }
14550       SendTimeRemaining(&first, TRUE);
14551     }
14552     if (first.useColors) {
14553       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14554     }
14555     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14556     SetMachineThinkingEnables();
14557     first.maybeThinking = TRUE;
14558     StartClocks();
14559     firstMove = FALSE;
14560
14561     if (appData.autoFlipView && !flipView) {
14562       flipView = !flipView;
14563       DrawPosition(FALSE, NULL);
14564       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14565     }
14566
14567     if(bookHit) { // [HGM] book: simulate book reply
14568         static char bookMove[MSG_SIZ]; // a bit generous?
14569
14570         programStats.nodes = programStats.depth = programStats.time =
14571         programStats.score = programStats.got_only_move = 0;
14572         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14573
14574         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14575         strcat(bookMove, bookHit);
14576         HandleMachineMove(bookMove, &first);
14577     }
14578 }
14579
14580 void
14581 MachineBlackEvent ()
14582 {
14583   char buf[MSG_SIZ];
14584   char *bookHit = NULL;
14585
14586     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14587         return;
14588
14589
14590     if (gameMode == PlayFromGameFile ||
14591         gameMode == TwoMachinesPlay  ||
14592         gameMode == Training         ||
14593         gameMode == AnalyzeMode      ||
14594         gameMode == EndOfGame)
14595         EditGameEvent();
14596
14597     if (gameMode == EditPosition)
14598         EditPositionDone(TRUE);
14599
14600     if (WhiteOnMove(currentMove)) {
14601         DisplayError(_("It is not Black's turn"), 0);
14602         return;
14603     }
14604
14605     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14606       ExitAnalyzeMode();
14607
14608     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14609         gameMode == AnalyzeFile)
14610         TruncateGame();
14611
14612     ResurrectChessProgram();    /* in case it isn't running */
14613     gameMode = MachinePlaysBlack;
14614     pausing = FALSE;
14615     ModeHighlight();
14616     SetGameInfo();
14617     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14618     DisplayTitle(buf);
14619     if (first.sendName) {
14620       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14621       SendToProgram(buf, &first);
14622     }
14623     if (first.sendTime) {
14624       if (first.useColors) {
14625         SendToProgram("white\n", &first); /*gnu kludge*/
14626       }
14627       SendTimeRemaining(&first, FALSE);
14628     }
14629     if (first.useColors) {
14630       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14631     }
14632     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14633     SetMachineThinkingEnables();
14634     first.maybeThinking = TRUE;
14635     StartClocks();
14636
14637     if (appData.autoFlipView && flipView) {
14638       flipView = !flipView;
14639       DrawPosition(FALSE, NULL);
14640       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14641     }
14642     if(bookHit) { // [HGM] book: simulate book reply
14643         static char bookMove[MSG_SIZ]; // a bit generous?
14644
14645         programStats.nodes = programStats.depth = programStats.time =
14646         programStats.score = programStats.got_only_move = 0;
14647         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14648
14649         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14650         strcat(bookMove, bookHit);
14651         HandleMachineMove(bookMove, &first);
14652     }
14653 }
14654
14655
14656 void
14657 DisplayTwoMachinesTitle ()
14658 {
14659     char buf[MSG_SIZ];
14660     if (appData.matchGames > 0) {
14661         if(appData.tourneyFile[0]) {
14662           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14663                    gameInfo.white, _("vs."), gameInfo.black,
14664                    nextGame+1, appData.matchGames+1,
14665                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14666         } else
14667         if (first.twoMachinesColor[0] == 'w') {
14668           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14669                    gameInfo.white, _("vs."),  gameInfo.black,
14670                    first.matchWins, second.matchWins,
14671                    matchGame - 1 - (first.matchWins + second.matchWins));
14672         } else {
14673           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14674                    gameInfo.white, _("vs."), gameInfo.black,
14675                    second.matchWins, first.matchWins,
14676                    matchGame - 1 - (first.matchWins + second.matchWins));
14677         }
14678     } else {
14679       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14680     }
14681     DisplayTitle(buf);
14682 }
14683
14684 void
14685 SettingsMenuIfReady ()
14686 {
14687   if (second.lastPing != second.lastPong) {
14688     DisplayMessage("", _("Waiting for second chess program"));
14689     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14690     return;
14691   }
14692   ThawUI();
14693   DisplayMessage("", "");
14694   SettingsPopUp(&second);
14695 }
14696
14697 int
14698 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14699 {
14700     char buf[MSG_SIZ];
14701     if (cps->pr == NoProc) {
14702         StartChessProgram(cps);
14703         if (cps->protocolVersion == 1) {
14704           retry();
14705           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14706         } else {
14707           /* kludge: allow timeout for initial "feature" command */
14708           if(retry != TwoMachinesEventIfReady) FreezeUI();
14709           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14710           DisplayMessage("", buf);
14711           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14712         }
14713         return 1;
14714     }
14715     return 0;
14716 }
14717
14718 void
14719 TwoMachinesEvent P((void))
14720 {
14721     int i;
14722     char buf[MSG_SIZ];
14723     ChessProgramState *onmove;
14724     char *bookHit = NULL;
14725     static int stalling = 0;
14726     TimeMark now;
14727     long wait;
14728
14729     if (appData.noChessProgram) return;
14730
14731     switch (gameMode) {
14732       case TwoMachinesPlay:
14733         return;
14734       case MachinePlaysWhite:
14735       case MachinePlaysBlack:
14736         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14737             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14738             return;
14739         }
14740         /* fall through */
14741       case BeginningOfGame:
14742       case PlayFromGameFile:
14743       case EndOfGame:
14744         EditGameEvent();
14745         if (gameMode != EditGame) return;
14746         break;
14747       case EditPosition:
14748         EditPositionDone(TRUE);
14749         break;
14750       case AnalyzeMode:
14751       case AnalyzeFile:
14752         ExitAnalyzeMode();
14753         break;
14754       case EditGame:
14755       default:
14756         break;
14757     }
14758
14759 //    forwardMostMove = currentMove;
14760     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14761     startingEngine = TRUE;
14762
14763     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14764
14765     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14766     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14767       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14768       return;
14769     }
14770     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14771
14772     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14773                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14774         startingEngine = matchMode = FALSE;
14775         DisplayError("second engine does not play this", 0);
14776         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14777         EditGameEvent(); // switch back to EditGame mode
14778         return;
14779     }
14780
14781     if(!stalling) {
14782       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14783       SendToProgram("force\n", &second);
14784       stalling = 1;
14785       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14786       return;
14787     }
14788     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14789     if(appData.matchPause>10000 || appData.matchPause<10)
14790                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14791     wait = SubtractTimeMarks(&now, &pauseStart);
14792     if(wait < appData.matchPause) {
14793         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14794         return;
14795     }
14796     // we are now committed to starting the game
14797     stalling = 0;
14798     DisplayMessage("", "");
14799     if (startedFromSetupPosition) {
14800         SendBoard(&second, backwardMostMove);
14801     if (appData.debugMode) {
14802         fprintf(debugFP, "Two Machines\n");
14803     }
14804     }
14805     for (i = backwardMostMove; i < forwardMostMove; i++) {
14806         SendMoveToProgram(i, &second);
14807     }
14808
14809     gameMode = TwoMachinesPlay;
14810     pausing = startingEngine = FALSE;
14811     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14812     SetGameInfo();
14813     DisplayTwoMachinesTitle();
14814     firstMove = TRUE;
14815     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14816         onmove = &first;
14817     } else {
14818         onmove = &second;
14819     }
14820     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14821     SendToProgram(first.computerString, &first);
14822     if (first.sendName) {
14823       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14824       SendToProgram(buf, &first);
14825     }
14826     SendToProgram(second.computerString, &second);
14827     if (second.sendName) {
14828       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14829       SendToProgram(buf, &second);
14830     }
14831
14832     ResetClocks();
14833     if (!first.sendTime || !second.sendTime) {
14834         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14835         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14836     }
14837     if (onmove->sendTime) {
14838       if (onmove->useColors) {
14839         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14840       }
14841       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14842     }
14843     if (onmove->useColors) {
14844       SendToProgram(onmove->twoMachinesColor, onmove);
14845     }
14846     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14847 //    SendToProgram("go\n", onmove);
14848     onmove->maybeThinking = TRUE;
14849     SetMachineThinkingEnables();
14850
14851     StartClocks();
14852
14853     if(bookHit) { // [HGM] book: simulate book reply
14854         static char bookMove[MSG_SIZ]; // a bit generous?
14855
14856         programStats.nodes = programStats.depth = programStats.time =
14857         programStats.score = programStats.got_only_move = 0;
14858         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14859
14860         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14861         strcat(bookMove, bookHit);
14862         savedMessage = bookMove; // args for deferred call
14863         savedState = onmove;
14864         ScheduleDelayedEvent(DeferredBookMove, 1);
14865     }
14866 }
14867
14868 void
14869 TrainingEvent ()
14870 {
14871     if (gameMode == Training) {
14872       SetTrainingModeOff();
14873       gameMode = PlayFromGameFile;
14874       DisplayMessage("", _("Training mode off"));
14875     } else {
14876       gameMode = Training;
14877       animateTraining = appData.animate;
14878
14879       /* make sure we are not already at the end of the game */
14880       if (currentMove < forwardMostMove) {
14881         SetTrainingModeOn();
14882         DisplayMessage("", _("Training mode on"));
14883       } else {
14884         gameMode = PlayFromGameFile;
14885         DisplayError(_("Already at end of game"), 0);
14886       }
14887     }
14888     ModeHighlight();
14889 }
14890
14891 void
14892 IcsClientEvent ()
14893 {
14894     if (!appData.icsActive) return;
14895     switch (gameMode) {
14896       case IcsPlayingWhite:
14897       case IcsPlayingBlack:
14898       case IcsObserving:
14899       case IcsIdle:
14900       case BeginningOfGame:
14901       case IcsExamining:
14902         return;
14903
14904       case EditGame:
14905         break;
14906
14907       case EditPosition:
14908         EditPositionDone(TRUE);
14909         break;
14910
14911       case AnalyzeMode:
14912       case AnalyzeFile:
14913         ExitAnalyzeMode();
14914         break;
14915
14916       default:
14917         EditGameEvent();
14918         break;
14919     }
14920
14921     gameMode = IcsIdle;
14922     ModeHighlight();
14923     return;
14924 }
14925
14926 void
14927 EditGameEvent ()
14928 {
14929     int i;
14930
14931     switch (gameMode) {
14932       case Training:
14933         SetTrainingModeOff();
14934         break;
14935       case MachinePlaysWhite:
14936       case MachinePlaysBlack:
14937       case BeginningOfGame:
14938         SendToProgram("force\n", &first);
14939         SetUserThinkingEnables();
14940         break;
14941       case PlayFromGameFile:
14942         (void) StopLoadGameTimer();
14943         if (gameFileFP != NULL) {
14944             gameFileFP = NULL;
14945         }
14946         break;
14947       case EditPosition:
14948         EditPositionDone(TRUE);
14949         break;
14950       case AnalyzeMode:
14951       case AnalyzeFile:
14952         ExitAnalyzeMode();
14953         SendToProgram("force\n", &first);
14954         break;
14955       case TwoMachinesPlay:
14956         GameEnds(EndOfFile, NULL, GE_PLAYER);
14957         ResurrectChessProgram();
14958         SetUserThinkingEnables();
14959         break;
14960       case EndOfGame:
14961         ResurrectChessProgram();
14962         break;
14963       case IcsPlayingBlack:
14964       case IcsPlayingWhite:
14965         DisplayError(_("Warning: You are still playing a game"), 0);
14966         break;
14967       case IcsObserving:
14968         DisplayError(_("Warning: You are still observing a game"), 0);
14969         break;
14970       case IcsExamining:
14971         DisplayError(_("Warning: You are still examining a game"), 0);
14972         break;
14973       case IcsIdle:
14974         break;
14975       case EditGame:
14976       default:
14977         return;
14978     }
14979
14980     pausing = FALSE;
14981     StopClocks();
14982     first.offeredDraw = second.offeredDraw = 0;
14983
14984     if (gameMode == PlayFromGameFile) {
14985         whiteTimeRemaining = timeRemaining[0][currentMove];
14986         blackTimeRemaining = timeRemaining[1][currentMove];
14987         DisplayTitle("");
14988     }
14989
14990     if (gameMode == MachinePlaysWhite ||
14991         gameMode == MachinePlaysBlack ||
14992         gameMode == TwoMachinesPlay ||
14993         gameMode == EndOfGame) {
14994         i = forwardMostMove;
14995         while (i > currentMove) {
14996             SendToProgram("undo\n", &first);
14997             i--;
14998         }
14999         if(!adjustedClock) {
15000         whiteTimeRemaining = timeRemaining[0][currentMove];
15001         blackTimeRemaining = timeRemaining[1][currentMove];
15002         DisplayBothClocks();
15003         }
15004         if (whiteFlag || blackFlag) {
15005             whiteFlag = blackFlag = 0;
15006         }
15007         DisplayTitle("");
15008     }
15009
15010     gameMode = EditGame;
15011     ModeHighlight();
15012     SetGameInfo();
15013 }
15014
15015
15016 void
15017 EditPositionEvent ()
15018 {
15019     if (gameMode == EditPosition) {
15020         EditGameEvent();
15021         return;
15022     }
15023
15024     EditGameEvent();
15025     if (gameMode != EditGame) return;
15026
15027     gameMode = EditPosition;
15028     ModeHighlight();
15029     SetGameInfo();
15030     if (currentMove > 0)
15031       CopyBoard(boards[0], boards[currentMove]);
15032
15033     blackPlaysFirst = !WhiteOnMove(currentMove);
15034     ResetClocks();
15035     currentMove = forwardMostMove = backwardMostMove = 0;
15036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15037     DisplayMove(-1);
15038     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15039 }
15040
15041 void
15042 ExitAnalyzeMode ()
15043 {
15044     /* [DM] icsEngineAnalyze - possible call from other functions */
15045     if (appData.icsEngineAnalyze) {
15046         appData.icsEngineAnalyze = FALSE;
15047
15048         DisplayMessage("",_("Close ICS engine analyze..."));
15049     }
15050     if (first.analysisSupport && first.analyzing) {
15051       SendToBoth("exit\n");
15052       first.analyzing = second.analyzing = FALSE;
15053     }
15054     thinkOutput[0] = NULLCHAR;
15055 }
15056
15057 void
15058 EditPositionDone (Boolean fakeRights)
15059 {
15060     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15061
15062     startedFromSetupPosition = TRUE;
15063     InitChessProgram(&first, FALSE);
15064     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15065       boards[0][EP_STATUS] = EP_NONE;
15066       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15067       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15068         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15069         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15070       } else boards[0][CASTLING][2] = NoRights;
15071       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15072         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15073         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15074       } else boards[0][CASTLING][5] = NoRights;
15075       if(gameInfo.variant == VariantSChess) {
15076         int i;
15077         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15078           boards[0][VIRGIN][i] = 0;
15079           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15080           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15081         }
15082       }
15083     }
15084     SendToProgram("force\n", &first);
15085     if (blackPlaysFirst) {
15086         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15087         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15088         currentMove = forwardMostMove = backwardMostMove = 1;
15089         CopyBoard(boards[1], boards[0]);
15090     } else {
15091         currentMove = forwardMostMove = backwardMostMove = 0;
15092     }
15093     SendBoard(&first, forwardMostMove);
15094     if (appData.debugMode) {
15095         fprintf(debugFP, "EditPosDone\n");
15096     }
15097     DisplayTitle("");
15098     DisplayMessage("", "");
15099     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15100     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15101     gameMode = EditGame;
15102     ModeHighlight();
15103     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15104     ClearHighlights(); /* [AS] */
15105 }
15106
15107 /* Pause for `ms' milliseconds */
15108 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15109 void
15110 TimeDelay (long ms)
15111 {
15112     TimeMark m1, m2;
15113
15114     GetTimeMark(&m1);
15115     do {
15116         GetTimeMark(&m2);
15117     } while (SubtractTimeMarks(&m2, &m1) < ms);
15118 }
15119
15120 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15121 void
15122 SendMultiLineToICS (char *buf)
15123 {
15124     char temp[MSG_SIZ+1], *p;
15125     int len;
15126
15127     len = strlen(buf);
15128     if (len > MSG_SIZ)
15129       len = MSG_SIZ;
15130
15131     strncpy(temp, buf, len);
15132     temp[len] = 0;
15133
15134     p = temp;
15135     while (*p) {
15136         if (*p == '\n' || *p == '\r')
15137           *p = ' ';
15138         ++p;
15139     }
15140
15141     strcat(temp, "\n");
15142     SendToICS(temp);
15143     SendToPlayer(temp, strlen(temp));
15144 }
15145
15146 void
15147 SetWhiteToPlayEvent ()
15148 {
15149     if (gameMode == EditPosition) {
15150         blackPlaysFirst = FALSE;
15151         DisplayBothClocks();    /* works because currentMove is 0 */
15152     } else if (gameMode == IcsExamining) {
15153         SendToICS(ics_prefix);
15154         SendToICS("tomove white\n");
15155     }
15156 }
15157
15158 void
15159 SetBlackToPlayEvent ()
15160 {
15161     if (gameMode == EditPosition) {
15162         blackPlaysFirst = TRUE;
15163         currentMove = 1;        /* kludge */
15164         DisplayBothClocks();
15165         currentMove = 0;
15166     } else if (gameMode == IcsExamining) {
15167         SendToICS(ics_prefix);
15168         SendToICS("tomove black\n");
15169     }
15170 }
15171
15172 void
15173 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15174 {
15175     char buf[MSG_SIZ];
15176     ChessSquare piece = boards[0][y][x];
15177     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15178     static int lastVariant;
15179
15180     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15181
15182     switch (selection) {
15183       case ClearBoard:
15184         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15185         MarkTargetSquares(1);
15186         CopyBoard(currentBoard, boards[0]);
15187         CopyBoard(menuBoard, initialPosition);
15188         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15189             SendToICS(ics_prefix);
15190             SendToICS("bsetup clear\n");
15191         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15192             SendToICS(ics_prefix);
15193             SendToICS("clearboard\n");
15194         } else {
15195             int nonEmpty = 0;
15196             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15197                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15198                 for (y = 0; y < BOARD_HEIGHT; y++) {
15199                     if (gameMode == IcsExamining) {
15200                         if (boards[currentMove][y][x] != EmptySquare) {
15201                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15202                                     AAA + x, ONE + y);
15203                             SendToICS(buf);
15204                         }
15205                     } else if(boards[0][y][x] != DarkSquare) {
15206                         if(boards[0][y][x] != p) nonEmpty++;
15207                         boards[0][y][x] = p;
15208                     }
15209                 }
15210             }
15211             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15212                 int r;
15213                 for(r = 0; r < BOARD_HEIGHT; r++) {
15214                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15215                     ChessSquare p = menuBoard[r][x];
15216                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15217                   }
15218                 }
15219                 DisplayMessage("Clicking clock again restores position", "");
15220                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15221                 if(!nonEmpty) { // asked to clear an empty board
15222                     CopyBoard(boards[0], menuBoard);
15223                 } else
15224                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15225                     CopyBoard(boards[0], initialPosition);
15226                 } else
15227                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15228                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15229                     CopyBoard(boards[0], erasedBoard);
15230                 } else
15231                     CopyBoard(erasedBoard, currentBoard);
15232
15233             }
15234         }
15235         if (gameMode == EditPosition) {
15236             DrawPosition(FALSE, boards[0]);
15237         }
15238         break;
15239
15240       case WhitePlay:
15241         SetWhiteToPlayEvent();
15242         break;
15243
15244       case BlackPlay:
15245         SetBlackToPlayEvent();
15246         break;
15247
15248       case EmptySquare:
15249         if (gameMode == IcsExamining) {
15250             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15251             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15252             SendToICS(buf);
15253         } else {
15254             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15255                 if(x == BOARD_LEFT-2) {
15256                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15257                     boards[0][y][1] = 0;
15258                 } else
15259                 if(x == BOARD_RGHT+1) {
15260                     if(y >= gameInfo.holdingsSize) break;
15261                     boards[0][y][BOARD_WIDTH-2] = 0;
15262                 } else break;
15263             }
15264             boards[0][y][x] = EmptySquare;
15265             DrawPosition(FALSE, boards[0]);
15266         }
15267         break;
15268
15269       case PromotePiece:
15270         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15271            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15272             selection = (ChessSquare) (PROMOTED piece);
15273         } else if(piece == EmptySquare) selection = WhiteSilver;
15274         else selection = (ChessSquare)((int)piece - 1);
15275         goto defaultlabel;
15276
15277       case DemotePiece:
15278         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15279            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15280             selection = (ChessSquare) (DEMOTED piece);
15281         } else if(piece == EmptySquare) selection = BlackSilver;
15282         else selection = (ChessSquare)((int)piece + 1);
15283         goto defaultlabel;
15284
15285       case WhiteQueen:
15286       case BlackQueen:
15287         if(gameInfo.variant == VariantShatranj ||
15288            gameInfo.variant == VariantXiangqi  ||
15289            gameInfo.variant == VariantCourier  ||
15290            gameInfo.variant == VariantASEAN    ||
15291            gameInfo.variant == VariantMakruk     )
15292             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15293         goto defaultlabel;
15294
15295       case WhiteKing:
15296       case BlackKing:
15297         if(gameInfo.variant == VariantXiangqi)
15298             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15299         if(gameInfo.variant == VariantKnightmate)
15300             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15301       default:
15302         defaultlabel:
15303         if (gameMode == IcsExamining) {
15304             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15305             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15306                      PieceToChar(selection), AAA + x, ONE + y);
15307             SendToICS(buf);
15308         } else {
15309             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15310                 int n;
15311                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15312                     n = PieceToNumber(selection - BlackPawn);
15313                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15314                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15315                     boards[0][BOARD_HEIGHT-1-n][1]++;
15316                 } else
15317                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15318                     n = PieceToNumber(selection);
15319                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15320                     boards[0][n][BOARD_WIDTH-1] = selection;
15321                     boards[0][n][BOARD_WIDTH-2]++;
15322                 }
15323             } else
15324             boards[0][y][x] = selection;
15325             DrawPosition(TRUE, boards[0]);
15326             ClearHighlights();
15327             fromX = fromY = -1;
15328         }
15329         break;
15330     }
15331 }
15332
15333
15334 void
15335 DropMenuEvent (ChessSquare selection, int x, int y)
15336 {
15337     ChessMove moveType;
15338
15339     switch (gameMode) {
15340       case IcsPlayingWhite:
15341       case MachinePlaysBlack:
15342         if (!WhiteOnMove(currentMove)) {
15343             DisplayMoveError(_("It is Black's turn"));
15344             return;
15345         }
15346         moveType = WhiteDrop;
15347         break;
15348       case IcsPlayingBlack:
15349       case MachinePlaysWhite:
15350         if (WhiteOnMove(currentMove)) {
15351             DisplayMoveError(_("It is White's turn"));
15352             return;
15353         }
15354         moveType = BlackDrop;
15355         break;
15356       case EditGame:
15357         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15358         break;
15359       default:
15360         return;
15361     }
15362
15363     if (moveType == BlackDrop && selection < BlackPawn) {
15364       selection = (ChessSquare) ((int) selection
15365                                  + (int) BlackPawn - (int) WhitePawn);
15366     }
15367     if (boards[currentMove][y][x] != EmptySquare) {
15368         DisplayMoveError(_("That square is occupied"));
15369         return;
15370     }
15371
15372     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15373 }
15374
15375 void
15376 AcceptEvent ()
15377 {
15378     /* Accept a pending offer of any kind from opponent */
15379
15380     if (appData.icsActive) {
15381         SendToICS(ics_prefix);
15382         SendToICS("accept\n");
15383     } else if (cmailMsgLoaded) {
15384         if (currentMove == cmailOldMove &&
15385             commentList[cmailOldMove] != NULL &&
15386             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15387                    "Black offers a draw" : "White offers a draw")) {
15388             TruncateGame();
15389             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15390             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15391         } else {
15392             DisplayError(_("There is no pending offer on this move"), 0);
15393             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15394         }
15395     } else {
15396         /* Not used for offers from chess program */
15397     }
15398 }
15399
15400 void
15401 DeclineEvent ()
15402 {
15403     /* Decline a pending offer of any kind from opponent */
15404
15405     if (appData.icsActive) {
15406         SendToICS(ics_prefix);
15407         SendToICS("decline\n");
15408     } else if (cmailMsgLoaded) {
15409         if (currentMove == cmailOldMove &&
15410             commentList[cmailOldMove] != NULL &&
15411             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15412                    "Black offers a draw" : "White offers a draw")) {
15413 #ifdef NOTDEF
15414             AppendComment(cmailOldMove, "Draw declined", TRUE);
15415             DisplayComment(cmailOldMove - 1, "Draw declined");
15416 #endif /*NOTDEF*/
15417         } else {
15418             DisplayError(_("There is no pending offer on this move"), 0);
15419         }
15420     } else {
15421         /* Not used for offers from chess program */
15422     }
15423 }
15424
15425 void
15426 RematchEvent ()
15427 {
15428     /* Issue ICS rematch command */
15429     if (appData.icsActive) {
15430         SendToICS(ics_prefix);
15431         SendToICS("rematch\n");
15432     }
15433 }
15434
15435 void
15436 CallFlagEvent ()
15437 {
15438     /* Call your opponent's flag (claim a win on time) */
15439     if (appData.icsActive) {
15440         SendToICS(ics_prefix);
15441         SendToICS("flag\n");
15442     } else {
15443         switch (gameMode) {
15444           default:
15445             return;
15446           case MachinePlaysWhite:
15447             if (whiteFlag) {
15448                 if (blackFlag)
15449                   GameEnds(GameIsDrawn, "Both players ran out of time",
15450                            GE_PLAYER);
15451                 else
15452                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15453             } else {
15454                 DisplayError(_("Your opponent is not out of time"), 0);
15455             }
15456             break;
15457           case MachinePlaysBlack:
15458             if (blackFlag) {
15459                 if (whiteFlag)
15460                   GameEnds(GameIsDrawn, "Both players ran out of time",
15461                            GE_PLAYER);
15462                 else
15463                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15464             } else {
15465                 DisplayError(_("Your opponent is not out of time"), 0);
15466             }
15467             break;
15468         }
15469     }
15470 }
15471
15472 void
15473 ClockClick (int which)
15474 {       // [HGM] code moved to back-end from winboard.c
15475         if(which) { // black clock
15476           if (gameMode == EditPosition || gameMode == IcsExamining) {
15477             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15478             SetBlackToPlayEvent();
15479           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15480                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15481           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15482           } else if (shiftKey) {
15483             AdjustClock(which, -1);
15484           } else if (gameMode == IcsPlayingWhite ||
15485                      gameMode == MachinePlaysBlack) {
15486             CallFlagEvent();
15487           }
15488         } else { // white clock
15489           if (gameMode == EditPosition || gameMode == IcsExamining) {
15490             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15491             SetWhiteToPlayEvent();
15492           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15493                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15494           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15495           } else if (shiftKey) {
15496             AdjustClock(which, -1);
15497           } else if (gameMode == IcsPlayingBlack ||
15498                    gameMode == MachinePlaysWhite) {
15499             CallFlagEvent();
15500           }
15501         }
15502 }
15503
15504 void
15505 DrawEvent ()
15506 {
15507     /* Offer draw or accept pending draw offer from opponent */
15508
15509     if (appData.icsActive) {
15510         /* Note: tournament rules require draw offers to be
15511            made after you make your move but before you punch
15512            your clock.  Currently ICS doesn't let you do that;
15513            instead, you immediately punch your clock after making
15514            a move, but you can offer a draw at any time. */
15515
15516         SendToICS(ics_prefix);
15517         SendToICS("draw\n");
15518         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15519     } else if (cmailMsgLoaded) {
15520         if (currentMove == cmailOldMove &&
15521             commentList[cmailOldMove] != NULL &&
15522             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15523                    "Black offers a draw" : "White offers a draw")) {
15524             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15526         } else if (currentMove == cmailOldMove + 1) {
15527             char *offer = WhiteOnMove(cmailOldMove) ?
15528               "White offers a draw" : "Black offers a draw";
15529             AppendComment(currentMove, offer, TRUE);
15530             DisplayComment(currentMove - 1, offer);
15531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15532         } else {
15533             DisplayError(_("You must make your move before offering a draw"), 0);
15534             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15535         }
15536     } else if (first.offeredDraw) {
15537         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15538     } else {
15539         if (first.sendDrawOffers) {
15540             SendToProgram("draw\n", &first);
15541             userOfferedDraw = TRUE;
15542         }
15543     }
15544 }
15545
15546 void
15547 AdjournEvent ()
15548 {
15549     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15550
15551     if (appData.icsActive) {
15552         SendToICS(ics_prefix);
15553         SendToICS("adjourn\n");
15554     } else {
15555         /* Currently GNU Chess doesn't offer or accept Adjourns */
15556     }
15557 }
15558
15559
15560 void
15561 AbortEvent ()
15562 {
15563     /* Offer Abort or accept pending Abort offer from opponent */
15564
15565     if (appData.icsActive) {
15566         SendToICS(ics_prefix);
15567         SendToICS("abort\n");
15568     } else {
15569         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15570     }
15571 }
15572
15573 void
15574 ResignEvent ()
15575 {
15576     /* Resign.  You can do this even if it's not your turn. */
15577
15578     if (appData.icsActive) {
15579         SendToICS(ics_prefix);
15580         SendToICS("resign\n");
15581     } else {
15582         switch (gameMode) {
15583           case MachinePlaysWhite:
15584             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15585             break;
15586           case MachinePlaysBlack:
15587             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15588             break;
15589           case EditGame:
15590             if (cmailMsgLoaded) {
15591                 TruncateGame();
15592                 if (WhiteOnMove(cmailOldMove)) {
15593                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15594                 } else {
15595                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15596                 }
15597                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15598             }
15599             break;
15600           default:
15601             break;
15602         }
15603     }
15604 }
15605
15606
15607 void
15608 StopObservingEvent ()
15609 {
15610     /* Stop observing current games */
15611     SendToICS(ics_prefix);
15612     SendToICS("unobserve\n");
15613 }
15614
15615 void
15616 StopExaminingEvent ()
15617 {
15618     /* Stop observing current game */
15619     SendToICS(ics_prefix);
15620     SendToICS("unexamine\n");
15621 }
15622
15623 void
15624 ForwardInner (int target)
15625 {
15626     int limit; int oldSeekGraphUp = seekGraphUp;
15627
15628     if (appData.debugMode)
15629         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15630                 target, currentMove, forwardMostMove);
15631
15632     if (gameMode == EditPosition)
15633       return;
15634
15635     seekGraphUp = FALSE;
15636     MarkTargetSquares(1);
15637     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15638
15639     if (gameMode == PlayFromGameFile && !pausing)
15640       PauseEvent();
15641
15642     if (gameMode == IcsExamining && pausing)
15643       limit = pauseExamForwardMostMove;
15644     else
15645       limit = forwardMostMove;
15646
15647     if (target > limit) target = limit;
15648
15649     if (target > 0 && moveList[target - 1][0]) {
15650         int fromX, fromY, toX, toY;
15651         toX = moveList[target - 1][2] - AAA;
15652         toY = moveList[target - 1][3] - ONE;
15653         if (moveList[target - 1][1] == '@') {
15654             if (appData.highlightLastMove) {
15655                 SetHighlights(-1, -1, toX, toY);
15656             }
15657         } else {
15658             int viaX = moveList[target - 1][5] - AAA;
15659             int viaY = moveList[target - 1][6] - ONE;
15660             fromX = moveList[target - 1][0] - AAA;
15661             fromY = moveList[target - 1][1] - ONE;
15662             if (target == currentMove + 1) {
15663                 if(moveList[target - 1][4] == ';') { // multi-leg
15664                     ChessSquare piece = boards[currentMove][viaY][viaX];
15665                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15666                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15667                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15668                     boards[currentMove][viaY][viaX] = piece;
15669                 } else
15670                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15671             }
15672             if (appData.highlightLastMove) {
15673                 SetHighlights(fromX, fromY, toX, toY);
15674             }
15675         }
15676     }
15677     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15678         gameMode == Training || gameMode == PlayFromGameFile ||
15679         gameMode == AnalyzeFile) {
15680         while (currentMove < target) {
15681             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15682             SendMoveToProgram(currentMove++, &first);
15683         }
15684     } else {
15685         currentMove = target;
15686     }
15687
15688     if (gameMode == EditGame || gameMode == EndOfGame) {
15689         whiteTimeRemaining = timeRemaining[0][currentMove];
15690         blackTimeRemaining = timeRemaining[1][currentMove];
15691     }
15692     DisplayBothClocks();
15693     DisplayMove(currentMove - 1);
15694     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15695     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15696     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15697         DisplayComment(currentMove - 1, commentList[currentMove]);
15698     }
15699     ClearMap(); // [HGM] exclude: invalidate map
15700 }
15701
15702
15703 void
15704 ForwardEvent ()
15705 {
15706     if (gameMode == IcsExamining && !pausing) {
15707         SendToICS(ics_prefix);
15708         SendToICS("forward\n");
15709     } else {
15710         ForwardInner(currentMove + 1);
15711     }
15712 }
15713
15714 void
15715 ToEndEvent ()
15716 {
15717     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15718         /* to optimze, we temporarily turn off analysis mode while we feed
15719          * the remaining moves to the engine. Otherwise we get analysis output
15720          * after each move.
15721          */
15722         if (first.analysisSupport) {
15723           SendToProgram("exit\nforce\n", &first);
15724           first.analyzing = FALSE;
15725         }
15726     }
15727
15728     if (gameMode == IcsExamining && !pausing) {
15729         SendToICS(ics_prefix);
15730         SendToICS("forward 999999\n");
15731     } else {
15732         ForwardInner(forwardMostMove);
15733     }
15734
15735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15736         /* we have fed all the moves, so reactivate analysis mode */
15737         SendToProgram("analyze\n", &first);
15738         first.analyzing = TRUE;
15739         /*first.maybeThinking = TRUE;*/
15740         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15741     }
15742 }
15743
15744 void
15745 BackwardInner (int target)
15746 {
15747     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15748
15749     if (appData.debugMode)
15750         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15751                 target, currentMove, forwardMostMove);
15752
15753     if (gameMode == EditPosition) return;
15754     seekGraphUp = FALSE;
15755     MarkTargetSquares(1);
15756     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15757     if (currentMove <= backwardMostMove) {
15758         ClearHighlights();
15759         DrawPosition(full_redraw, boards[currentMove]);
15760         return;
15761     }
15762     if (gameMode == PlayFromGameFile && !pausing)
15763       PauseEvent();
15764
15765     if (moveList[target][0]) {
15766         int fromX, fromY, toX, toY;
15767         toX = moveList[target][2] - AAA;
15768         toY = moveList[target][3] - ONE;
15769         if (moveList[target][1] == '@') {
15770             if (appData.highlightLastMove) {
15771                 SetHighlights(-1, -1, toX, toY);
15772             }
15773         } else {
15774             fromX = moveList[target][0] - AAA;
15775             fromY = moveList[target][1] - ONE;
15776             if (target == currentMove - 1) {
15777                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15778             }
15779             if (appData.highlightLastMove) {
15780                 SetHighlights(fromX, fromY, toX, toY);
15781             }
15782         }
15783     }
15784     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15785         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15786         while (currentMove > target) {
15787             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15788                 // null move cannot be undone. Reload program with move history before it.
15789                 int i;
15790                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15791                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15792                 }
15793                 SendBoard(&first, i);
15794               if(second.analyzing) SendBoard(&second, i);
15795                 for(currentMove=i; currentMove<target; currentMove++) {
15796                     SendMoveToProgram(currentMove, &first);
15797                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15798                 }
15799                 break;
15800             }
15801             SendToBoth("undo\n");
15802             currentMove--;
15803         }
15804     } else {
15805         currentMove = target;
15806     }
15807
15808     if (gameMode == EditGame || gameMode == EndOfGame) {
15809         whiteTimeRemaining = timeRemaining[0][currentMove];
15810         blackTimeRemaining = timeRemaining[1][currentMove];
15811     }
15812     DisplayBothClocks();
15813     DisplayMove(currentMove - 1);
15814     DrawPosition(full_redraw, boards[currentMove]);
15815     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15816     // [HGM] PV info: routine tests if comment empty
15817     DisplayComment(currentMove - 1, commentList[currentMove]);
15818     ClearMap(); // [HGM] exclude: invalidate map
15819 }
15820
15821 void
15822 BackwardEvent ()
15823 {
15824     if (gameMode == IcsExamining && !pausing) {
15825         SendToICS(ics_prefix);
15826         SendToICS("backward\n");
15827     } else {
15828         BackwardInner(currentMove - 1);
15829     }
15830 }
15831
15832 void
15833 ToStartEvent ()
15834 {
15835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15836         /* to optimize, we temporarily turn off analysis mode while we undo
15837          * all the moves. Otherwise we get analysis output after each undo.
15838          */
15839         if (first.analysisSupport) {
15840           SendToProgram("exit\nforce\n", &first);
15841           first.analyzing = FALSE;
15842         }
15843     }
15844
15845     if (gameMode == IcsExamining && !pausing) {
15846         SendToICS(ics_prefix);
15847         SendToICS("backward 999999\n");
15848     } else {
15849         BackwardInner(backwardMostMove);
15850     }
15851
15852     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15853         /* we have fed all the moves, so reactivate analysis mode */
15854         SendToProgram("analyze\n", &first);
15855         first.analyzing = TRUE;
15856         /*first.maybeThinking = TRUE;*/
15857         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15858     }
15859 }
15860
15861 void
15862 ToNrEvent (int to)
15863 {
15864   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15865   if (to >= forwardMostMove) to = forwardMostMove;
15866   if (to <= backwardMostMove) to = backwardMostMove;
15867   if (to < currentMove) {
15868     BackwardInner(to);
15869   } else {
15870     ForwardInner(to);
15871   }
15872 }
15873
15874 void
15875 RevertEvent (Boolean annotate)
15876 {
15877     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15878         return;
15879     }
15880     if (gameMode != IcsExamining) {
15881         DisplayError(_("You are not examining a game"), 0);
15882         return;
15883     }
15884     if (pausing) {
15885         DisplayError(_("You can't revert while pausing"), 0);
15886         return;
15887     }
15888     SendToICS(ics_prefix);
15889     SendToICS("revert\n");
15890 }
15891
15892 void
15893 RetractMoveEvent ()
15894 {
15895     switch (gameMode) {
15896       case MachinePlaysWhite:
15897       case MachinePlaysBlack:
15898         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15899             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15900             return;
15901         }
15902         if (forwardMostMove < 2) return;
15903         currentMove = forwardMostMove = forwardMostMove - 2;
15904         whiteTimeRemaining = timeRemaining[0][currentMove];
15905         blackTimeRemaining = timeRemaining[1][currentMove];
15906         DisplayBothClocks();
15907         DisplayMove(currentMove - 1);
15908         ClearHighlights();/*!! could figure this out*/
15909         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15910         SendToProgram("remove\n", &first);
15911         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15912         break;
15913
15914       case BeginningOfGame:
15915       default:
15916         break;
15917
15918       case IcsPlayingWhite:
15919       case IcsPlayingBlack:
15920         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15921             SendToICS(ics_prefix);
15922             SendToICS("takeback 2\n");
15923         } else {
15924             SendToICS(ics_prefix);
15925             SendToICS("takeback 1\n");
15926         }
15927         break;
15928     }
15929 }
15930
15931 void
15932 MoveNowEvent ()
15933 {
15934     ChessProgramState *cps;
15935
15936     switch (gameMode) {
15937       case MachinePlaysWhite:
15938         if (!WhiteOnMove(forwardMostMove)) {
15939             DisplayError(_("It is your turn"), 0);
15940             return;
15941         }
15942         cps = &first;
15943         break;
15944       case MachinePlaysBlack:
15945         if (WhiteOnMove(forwardMostMove)) {
15946             DisplayError(_("It is your turn"), 0);
15947             return;
15948         }
15949         cps = &first;
15950         break;
15951       case TwoMachinesPlay:
15952         if (WhiteOnMove(forwardMostMove) ==
15953             (first.twoMachinesColor[0] == 'w')) {
15954             cps = &first;
15955         } else {
15956             cps = &second;
15957         }
15958         break;
15959       case BeginningOfGame:
15960       default:
15961         return;
15962     }
15963     SendToProgram("?\n", cps);
15964 }
15965
15966 void
15967 TruncateGameEvent ()
15968 {
15969     EditGameEvent();
15970     if (gameMode != EditGame) return;
15971     TruncateGame();
15972 }
15973
15974 void
15975 TruncateGame ()
15976 {
15977     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15978     if (forwardMostMove > currentMove) {
15979         if (gameInfo.resultDetails != NULL) {
15980             free(gameInfo.resultDetails);
15981             gameInfo.resultDetails = NULL;
15982             gameInfo.result = GameUnfinished;
15983         }
15984         forwardMostMove = currentMove;
15985         HistorySet(parseList, backwardMostMove, forwardMostMove,
15986                    currentMove-1);
15987     }
15988 }
15989
15990 void
15991 HintEvent ()
15992 {
15993     if (appData.noChessProgram) return;
15994     switch (gameMode) {
15995       case MachinePlaysWhite:
15996         if (WhiteOnMove(forwardMostMove)) {
15997             DisplayError(_("Wait until your turn."), 0);
15998             return;
15999         }
16000         break;
16001       case BeginningOfGame:
16002       case MachinePlaysBlack:
16003         if (!WhiteOnMove(forwardMostMove)) {
16004             DisplayError(_("Wait until your turn."), 0);
16005             return;
16006         }
16007         break;
16008       default:
16009         DisplayError(_("No hint available"), 0);
16010         return;
16011     }
16012     SendToProgram("hint\n", &first);
16013     hintRequested = TRUE;
16014 }
16015
16016 int
16017 SaveSelected (FILE *g, int dummy, char *dummy2)
16018 {
16019     ListGame * lg = (ListGame *) gameList.head;
16020     int nItem, cnt=0;
16021     FILE *f;
16022
16023     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16024         DisplayError(_("Game list not loaded or empty"), 0);
16025         return 0;
16026     }
16027
16028     creatingBook = TRUE; // suppresses stuff during load game
16029
16030     /* Get list size */
16031     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16032         if(lg->position >= 0) { // selected?
16033             LoadGame(f, nItem, "", TRUE);
16034             SaveGamePGN2(g); // leaves g open
16035             cnt++; DoEvents();
16036         }
16037         lg = (ListGame *) lg->node.succ;
16038     }
16039
16040     fclose(g);
16041     creatingBook = FALSE;
16042
16043     return cnt;
16044 }
16045
16046 void
16047 CreateBookEvent ()
16048 {
16049     ListGame * lg = (ListGame *) gameList.head;
16050     FILE *f, *g;
16051     int nItem;
16052     static int secondTime = FALSE;
16053
16054     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16055         DisplayError(_("Game list not loaded or empty"), 0);
16056         return;
16057     }
16058
16059     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16060         fclose(g);
16061         secondTime++;
16062         DisplayNote(_("Book file exists! Try again for overwrite."));
16063         return;
16064     }
16065
16066     creatingBook = TRUE;
16067     secondTime = FALSE;
16068
16069     /* Get list size */
16070     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16071         if(lg->position >= 0) {
16072             LoadGame(f, nItem, "", TRUE);
16073             AddGameToBook(TRUE);
16074             DoEvents();
16075         }
16076         lg = (ListGame *) lg->node.succ;
16077     }
16078
16079     creatingBook = FALSE;
16080     FlushBook();
16081 }
16082
16083 void
16084 BookEvent ()
16085 {
16086     if (appData.noChessProgram) return;
16087     switch (gameMode) {
16088       case MachinePlaysWhite:
16089         if (WhiteOnMove(forwardMostMove)) {
16090             DisplayError(_("Wait until your turn."), 0);
16091             return;
16092         }
16093         break;
16094       case BeginningOfGame:
16095       case MachinePlaysBlack:
16096         if (!WhiteOnMove(forwardMostMove)) {
16097             DisplayError(_("Wait until your turn."), 0);
16098             return;
16099         }
16100         break;
16101       case EditPosition:
16102         EditPositionDone(TRUE);
16103         break;
16104       case TwoMachinesPlay:
16105         return;
16106       default:
16107         break;
16108     }
16109     SendToProgram("bk\n", &first);
16110     bookOutput[0] = NULLCHAR;
16111     bookRequested = TRUE;
16112 }
16113
16114 void
16115 AboutGameEvent ()
16116 {
16117     char *tags = PGNTags(&gameInfo);
16118     TagsPopUp(tags, CmailMsg());
16119     free(tags);
16120 }
16121
16122 /* end button procedures */
16123
16124 void
16125 PrintPosition (FILE *fp, int move)
16126 {
16127     int i, j;
16128
16129     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16130         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16131             char c = PieceToChar(boards[move][i][j]);
16132             fputc(c == 'x' ? '.' : c, fp);
16133             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16134         }
16135     }
16136     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16137       fprintf(fp, "white to play\n");
16138     else
16139       fprintf(fp, "black to play\n");
16140 }
16141
16142 void
16143 PrintOpponents (FILE *fp)
16144 {
16145     if (gameInfo.white != NULL) {
16146         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16147     } else {
16148         fprintf(fp, "\n");
16149     }
16150 }
16151
16152 /* Find last component of program's own name, using some heuristics */
16153 void
16154 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16155 {
16156     char *p, *q, c;
16157     int local = (strcmp(host, "localhost") == 0);
16158     while (!local && (p = strchr(prog, ';')) != NULL) {
16159         p++;
16160         while (*p == ' ') p++;
16161         prog = p;
16162     }
16163     if (*prog == '"' || *prog == '\'') {
16164         q = strchr(prog + 1, *prog);
16165     } else {
16166         q = strchr(prog, ' ');
16167     }
16168     if (q == NULL) q = prog + strlen(prog);
16169     p = q;
16170     while (p >= prog && *p != '/' && *p != '\\') p--;
16171     p++;
16172     if(p == prog && *p == '"') p++;
16173     c = *q; *q = 0;
16174     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16175     memcpy(buf, p, q - p);
16176     buf[q - p] = NULLCHAR;
16177     if (!local) {
16178         strcat(buf, "@");
16179         strcat(buf, host);
16180     }
16181 }
16182
16183 char *
16184 TimeControlTagValue ()
16185 {
16186     char buf[MSG_SIZ];
16187     if (!appData.clockMode) {
16188       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16189     } else if (movesPerSession > 0) {
16190       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16191     } else if (timeIncrement == 0) {
16192       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16193     } else {
16194       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16195     }
16196     return StrSave(buf);
16197 }
16198
16199 void
16200 SetGameInfo ()
16201 {
16202     /* This routine is used only for certain modes */
16203     VariantClass v = gameInfo.variant;
16204     ChessMove r = GameUnfinished;
16205     char *p = NULL;
16206
16207     if(keepInfo) return;
16208
16209     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16210         r = gameInfo.result;
16211         p = gameInfo.resultDetails;
16212         gameInfo.resultDetails = NULL;
16213     }
16214     ClearGameInfo(&gameInfo);
16215     gameInfo.variant = v;
16216
16217     switch (gameMode) {
16218       case MachinePlaysWhite:
16219         gameInfo.event = StrSave( appData.pgnEventHeader );
16220         gameInfo.site = StrSave(HostName());
16221         gameInfo.date = PGNDate();
16222         gameInfo.round = StrSave("-");
16223         gameInfo.white = StrSave(first.tidy);
16224         gameInfo.black = StrSave(UserName());
16225         gameInfo.timeControl = TimeControlTagValue();
16226         break;
16227
16228       case MachinePlaysBlack:
16229         gameInfo.event = StrSave( appData.pgnEventHeader );
16230         gameInfo.site = StrSave(HostName());
16231         gameInfo.date = PGNDate();
16232         gameInfo.round = StrSave("-");
16233         gameInfo.white = StrSave(UserName());
16234         gameInfo.black = StrSave(first.tidy);
16235         gameInfo.timeControl = TimeControlTagValue();
16236         break;
16237
16238       case TwoMachinesPlay:
16239         gameInfo.event = StrSave( appData.pgnEventHeader );
16240         gameInfo.site = StrSave(HostName());
16241         gameInfo.date = PGNDate();
16242         if (roundNr > 0) {
16243             char buf[MSG_SIZ];
16244             snprintf(buf, MSG_SIZ, "%d", roundNr);
16245             gameInfo.round = StrSave(buf);
16246         } else {
16247             gameInfo.round = StrSave("-");
16248         }
16249         if (first.twoMachinesColor[0] == 'w') {
16250             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16251             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16252         } else {
16253             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16254             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16255         }
16256         gameInfo.timeControl = TimeControlTagValue();
16257         break;
16258
16259       case EditGame:
16260         gameInfo.event = StrSave("Edited game");
16261         gameInfo.site = StrSave(HostName());
16262         gameInfo.date = PGNDate();
16263         gameInfo.round = StrSave("-");
16264         gameInfo.white = StrSave("-");
16265         gameInfo.black = StrSave("-");
16266         gameInfo.result = r;
16267         gameInfo.resultDetails = p;
16268         break;
16269
16270       case EditPosition:
16271         gameInfo.event = StrSave("Edited position");
16272         gameInfo.site = StrSave(HostName());
16273         gameInfo.date = PGNDate();
16274         gameInfo.round = StrSave("-");
16275         gameInfo.white = StrSave("-");
16276         gameInfo.black = StrSave("-");
16277         break;
16278
16279       case IcsPlayingWhite:
16280       case IcsPlayingBlack:
16281       case IcsObserving:
16282       case IcsExamining:
16283         break;
16284
16285       case PlayFromGameFile:
16286         gameInfo.event = StrSave("Game from non-PGN file");
16287         gameInfo.site = StrSave(HostName());
16288         gameInfo.date = PGNDate();
16289         gameInfo.round = StrSave("-");
16290         gameInfo.white = StrSave("?");
16291         gameInfo.black = StrSave("?");
16292         break;
16293
16294       default:
16295         break;
16296     }
16297 }
16298
16299 void
16300 ReplaceComment (int index, char *text)
16301 {
16302     int len;
16303     char *p;
16304     float score;
16305
16306     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16307        pvInfoList[index-1].depth == len &&
16308        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16309        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16310     while (*text == '\n') text++;
16311     len = strlen(text);
16312     while (len > 0 && text[len - 1] == '\n') len--;
16313
16314     if (commentList[index] != NULL)
16315       free(commentList[index]);
16316
16317     if (len == 0) {
16318         commentList[index] = NULL;
16319         return;
16320     }
16321   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16322       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16323       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16324     commentList[index] = (char *) malloc(len + 2);
16325     strncpy(commentList[index], text, len);
16326     commentList[index][len] = '\n';
16327     commentList[index][len + 1] = NULLCHAR;
16328   } else {
16329     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16330     char *p;
16331     commentList[index] = (char *) malloc(len + 7);
16332     safeStrCpy(commentList[index], "{\n", 3);
16333     safeStrCpy(commentList[index]+2, text, len+1);
16334     commentList[index][len+2] = NULLCHAR;
16335     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16336     strcat(commentList[index], "\n}\n");
16337   }
16338 }
16339
16340 void
16341 CrushCRs (char *text)
16342 {
16343   char *p = text;
16344   char *q = text;
16345   char ch;
16346
16347   do {
16348     ch = *p++;
16349     if (ch == '\r') continue;
16350     *q++ = ch;
16351   } while (ch != '\0');
16352 }
16353
16354 void
16355 AppendComment (int index, char *text, Boolean addBraces)
16356 /* addBraces  tells if we should add {} */
16357 {
16358     int oldlen, len;
16359     char *old;
16360
16361 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16362     if(addBraces == 3) addBraces = 0; else // force appending literally
16363     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16364
16365     CrushCRs(text);
16366     while (*text == '\n') text++;
16367     len = strlen(text);
16368     while (len > 0 && text[len - 1] == '\n') len--;
16369     text[len] = NULLCHAR;
16370
16371     if (len == 0) return;
16372
16373     if (commentList[index] != NULL) {
16374       Boolean addClosingBrace = addBraces;
16375         old = commentList[index];
16376         oldlen = strlen(old);
16377         while(commentList[index][oldlen-1] ==  '\n')
16378           commentList[index][--oldlen] = NULLCHAR;
16379         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16380         safeStrCpy(commentList[index], old, oldlen + len + 6);
16381         free(old);
16382         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16383         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16384           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16385           while (*text == '\n') { text++; len--; }
16386           commentList[index][--oldlen] = NULLCHAR;
16387       }
16388         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16389         else          strcat(commentList[index], "\n");
16390         strcat(commentList[index], text);
16391         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16392         else          strcat(commentList[index], "\n");
16393     } else {
16394         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16395         if(addBraces)
16396           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16397         else commentList[index][0] = NULLCHAR;
16398         strcat(commentList[index], text);
16399         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16400         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16401     }
16402 }
16403
16404 static char *
16405 FindStr (char * text, char * sub_text)
16406 {
16407     char * result = strstr( text, sub_text );
16408
16409     if( result != NULL ) {
16410         result += strlen( sub_text );
16411     }
16412
16413     return result;
16414 }
16415
16416 /* [AS] Try to extract PV info from PGN comment */
16417 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16418 char *
16419 GetInfoFromComment (int index, char * text)
16420 {
16421     char * sep = text, *p;
16422
16423     if( text != NULL && index > 0 ) {
16424         int score = 0;
16425         int depth = 0;
16426         int time = -1, sec = 0, deci;
16427         char * s_eval = FindStr( text, "[%eval " );
16428         char * s_emt = FindStr( text, "[%emt " );
16429 #if 0
16430         if( s_eval != NULL || s_emt != NULL ) {
16431 #else
16432         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16433 #endif
16434             /* New style */
16435             char delim;
16436
16437             if( s_eval != NULL ) {
16438                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16439                     return text;
16440                 }
16441
16442                 if( delim != ']' ) {
16443                     return text;
16444                 }
16445             }
16446
16447             if( s_emt != NULL ) {
16448             }
16449                 return text;
16450         }
16451         else {
16452             /* We expect something like: [+|-]nnn.nn/dd */
16453             int score_lo = 0;
16454
16455             if(*text != '{') return text; // [HGM] braces: must be normal comment
16456
16457             sep = strchr( text, '/' );
16458             if( sep == NULL || sep < (text+4) ) {
16459                 return text;
16460             }
16461
16462             p = text;
16463             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16464             if(p[1] == '(') { // comment starts with PV
16465                p = strchr(p, ')'); // locate end of PV
16466                if(p == NULL || sep < p+5) return text;
16467                // at this point we have something like "{(.*) +0.23/6 ..."
16468                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16469                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16470                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16471             }
16472             time = -1; sec = -1; deci = -1;
16473             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16474                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16475                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16476                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16477                 return text;
16478             }
16479
16480             if( score_lo < 0 || score_lo >= 100 ) {
16481                 return text;
16482             }
16483
16484             if(sec >= 0) time = 600*time + 10*sec; else
16485             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16486
16487             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16488
16489             /* [HGM] PV time: now locate end of PV info */
16490             while( *++sep >= '0' && *sep <= '9'); // strip depth
16491             if(time >= 0)
16492             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16493             if(sec >= 0)
16494             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16495             if(deci >= 0)
16496             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16497             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16498         }
16499
16500         if( depth <= 0 ) {
16501             return text;
16502         }
16503
16504         if( time < 0 ) {
16505             time = -1;
16506         }
16507
16508         pvInfoList[index-1].depth = depth;
16509         pvInfoList[index-1].score = score;
16510         pvInfoList[index-1].time  = 10*time; // centi-sec
16511         if(*sep == '}') *sep = 0; else *--sep = '{';
16512         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16513     }
16514     return sep;
16515 }
16516
16517 void
16518 SendToProgram (char *message, ChessProgramState *cps)
16519 {
16520     int count, outCount, error;
16521     char buf[MSG_SIZ];
16522
16523     if (cps->pr == NoProc) return;
16524     Attention(cps);
16525
16526     if (appData.debugMode) {
16527         TimeMark now;
16528         GetTimeMark(&now);
16529         fprintf(debugFP, "%ld >%-6s: %s",
16530                 SubtractTimeMarks(&now, &programStartTime),
16531                 cps->which, message);
16532         if(serverFP)
16533             fprintf(serverFP, "%ld >%-6s: %s",
16534                 SubtractTimeMarks(&now, &programStartTime),
16535                 cps->which, message), fflush(serverFP);
16536     }
16537
16538     count = strlen(message);
16539     outCount = OutputToProcess(cps->pr, message, count, &error);
16540     if (outCount < count && !exiting
16541                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16542       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16543       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16544         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16545             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16546                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16547                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16548                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16549             } else {
16550                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16551                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16552                 gameInfo.result = res;
16553             }
16554             gameInfo.resultDetails = StrSave(buf);
16555         }
16556         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16557         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16558     }
16559 }
16560
16561 void
16562 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16563 {
16564     char *end_str;
16565     char buf[MSG_SIZ];
16566     ChessProgramState *cps = (ChessProgramState *)closure;
16567
16568     if (isr != cps->isr) return; /* Killed intentionally */
16569     if (count <= 0) {
16570         if (count == 0) {
16571             RemoveInputSource(cps->isr);
16572             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16573                     _(cps->which), cps->program);
16574             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16575             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16576                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16577                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16578                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16579                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16580                 } else {
16581                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16582                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16583                     gameInfo.result = res;
16584                 }
16585                 gameInfo.resultDetails = StrSave(buf);
16586             }
16587             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16588             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16589         } else {
16590             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16591                     _(cps->which), cps->program);
16592             RemoveInputSource(cps->isr);
16593
16594             /* [AS] Program is misbehaving badly... kill it */
16595             if( count == -2 ) {
16596                 DestroyChildProcess( cps->pr, 9 );
16597                 cps->pr = NoProc;
16598             }
16599
16600             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16601         }
16602         return;
16603     }
16604
16605     if ((end_str = strchr(message, '\r')) != NULL)
16606       *end_str = NULLCHAR;
16607     if ((end_str = strchr(message, '\n')) != NULL)
16608       *end_str = NULLCHAR;
16609
16610     if (appData.debugMode) {
16611         TimeMark now; int print = 1;
16612         char *quote = ""; char c; int i;
16613
16614         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16615                 char start = message[0];
16616                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16617                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16618                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16619                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16620                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16621                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16622                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16623                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16624                    sscanf(message, "hint: %c", &c)!=1 &&
16625                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16626                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16627                     print = (appData.engineComments >= 2);
16628                 }
16629                 message[0] = start; // restore original message
16630         }
16631         if(print) {
16632                 GetTimeMark(&now);
16633                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16634                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16635                         quote,
16636                         message);
16637                 if(serverFP)
16638                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16639                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16640                         quote,
16641                         message), fflush(serverFP);
16642         }
16643     }
16644
16645     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16646     if (appData.icsEngineAnalyze) {
16647         if (strstr(message, "whisper") != NULL ||
16648              strstr(message, "kibitz") != NULL ||
16649             strstr(message, "tellics") != NULL) return;
16650     }
16651
16652     HandleMachineMove(message, cps);
16653 }
16654
16655
16656 void
16657 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16658 {
16659     char buf[MSG_SIZ];
16660     int seconds;
16661
16662     if( timeControl_2 > 0 ) {
16663         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16664             tc = timeControl_2;
16665         }
16666     }
16667     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16668     inc /= cps->timeOdds;
16669     st  /= cps->timeOdds;
16670
16671     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16672
16673     if (st > 0) {
16674       /* Set exact time per move, normally using st command */
16675       if (cps->stKludge) {
16676         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16677         seconds = st % 60;
16678         if (seconds == 0) {
16679           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16680         } else {
16681           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16682         }
16683       } else {
16684         snprintf(buf, MSG_SIZ, "st %d\n", st);
16685       }
16686     } else {
16687       /* Set conventional or incremental time control, using level command */
16688       if (seconds == 0) {
16689         /* Note old gnuchess bug -- minutes:seconds used to not work.
16690            Fixed in later versions, but still avoid :seconds
16691            when seconds is 0. */
16692         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16693       } else {
16694         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16695                  seconds, inc/1000.);
16696       }
16697     }
16698     SendToProgram(buf, cps);
16699
16700     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16701     /* Orthogonally, limit search to given depth */
16702     if (sd > 0) {
16703       if (cps->sdKludge) {
16704         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16705       } else {
16706         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16707       }
16708       SendToProgram(buf, cps);
16709     }
16710
16711     if(cps->nps >= 0) { /* [HGM] nps */
16712         if(cps->supportsNPS == FALSE)
16713           cps->nps = -1; // don't use if engine explicitly says not supported!
16714         else {
16715           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16716           SendToProgram(buf, cps);
16717         }
16718     }
16719 }
16720
16721 ChessProgramState *
16722 WhitePlayer ()
16723 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16724 {
16725     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16726        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16727         return &second;
16728     return &first;
16729 }
16730
16731 void
16732 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16733 {
16734     char message[MSG_SIZ];
16735     long time, otime;
16736
16737     /* Note: this routine must be called when the clocks are stopped
16738        or when they have *just* been set or switched; otherwise
16739        it will be off by the time since the current tick started.
16740     */
16741     if (machineWhite) {
16742         time = whiteTimeRemaining / 10;
16743         otime = blackTimeRemaining / 10;
16744     } else {
16745         time = blackTimeRemaining / 10;
16746         otime = whiteTimeRemaining / 10;
16747     }
16748     /* [HGM] translate opponent's time by time-odds factor */
16749     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16750
16751     if (time <= 0) time = 1;
16752     if (otime <= 0) otime = 1;
16753
16754     snprintf(message, MSG_SIZ, "time %ld\n", time);
16755     SendToProgram(message, cps);
16756
16757     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16758     SendToProgram(message, cps);
16759 }
16760
16761 char *
16762 EngineDefinedVariant (ChessProgramState *cps, int n)
16763 {   // return name of n-th unknown variant that engine supports
16764     static char buf[MSG_SIZ];
16765     char *p, *s = cps->variants;
16766     if(!s) return NULL;
16767     do { // parse string from variants feature
16768       VariantClass v;
16769         p = strchr(s, ',');
16770         if(p) *p = NULLCHAR;
16771       v = StringToVariant(s);
16772       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16773         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16774             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16775                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16776                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16777                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16778             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16779         }
16780         if(p) *p++ = ',';
16781         if(n < 0) return buf;
16782     } while(s = p);
16783     return NULL;
16784 }
16785
16786 int
16787 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16788 {
16789   char buf[MSG_SIZ];
16790   int len = strlen(name);
16791   int val;
16792
16793   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16794     (*p) += len + 1;
16795     sscanf(*p, "%d", &val);
16796     *loc = (val != 0);
16797     while (**p && **p != ' ')
16798       (*p)++;
16799     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16800     SendToProgram(buf, cps);
16801     return TRUE;
16802   }
16803   return FALSE;
16804 }
16805
16806 int
16807 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16808 {
16809   char buf[MSG_SIZ];
16810   int len = strlen(name);
16811   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16812     (*p) += len + 1;
16813     sscanf(*p, "%d", loc);
16814     while (**p && **p != ' ') (*p)++;
16815     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16816     SendToProgram(buf, cps);
16817     return TRUE;
16818   }
16819   return FALSE;
16820 }
16821
16822 int
16823 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16824 {
16825   char buf[MSG_SIZ];
16826   int len = strlen(name);
16827   if (strncmp((*p), name, len) == 0
16828       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16829     (*p) += len + 2;
16830     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16831     sscanf(*p, "%[^\"]", *loc);
16832     while (**p && **p != '\"') (*p)++;
16833     if (**p == '\"') (*p)++;
16834     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16835     SendToProgram(buf, cps);
16836     return TRUE;
16837   }
16838   return FALSE;
16839 }
16840
16841 int
16842 ParseOption (Option *opt, ChessProgramState *cps)
16843 // [HGM] options: process the string that defines an engine option, and determine
16844 // name, type, default value, and allowed value range
16845 {
16846         char *p, *q, buf[MSG_SIZ];
16847         int n, min = (-1)<<31, max = 1<<31, def;
16848
16849         if(p = strstr(opt->name, " -spin ")) {
16850             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16851             if(max < min) max = min; // enforce consistency
16852             if(def < min) def = min;
16853             if(def > max) def = max;
16854             opt->value = def;
16855             opt->min = min;
16856             opt->max = max;
16857             opt->type = Spin;
16858         } else if((p = strstr(opt->name, " -slider "))) {
16859             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16860             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16861             if(max < min) max = min; // enforce consistency
16862             if(def < min) def = min;
16863             if(def > max) def = max;
16864             opt->value = def;
16865             opt->min = min;
16866             opt->max = max;
16867             opt->type = Spin; // Slider;
16868         } else if((p = strstr(opt->name, " -string "))) {
16869             opt->textValue = p+9;
16870             opt->type = TextBox;
16871         } else if((p = strstr(opt->name, " -file "))) {
16872             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16873             opt->textValue = p+7;
16874             opt->type = FileName; // FileName;
16875         } else if((p = strstr(opt->name, " -path "))) {
16876             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16877             opt->textValue = p+7;
16878             opt->type = PathName; // PathName;
16879         } else if(p = strstr(opt->name, " -check ")) {
16880             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16881             opt->value = (def != 0);
16882             opt->type = CheckBox;
16883         } else if(p = strstr(opt->name, " -combo ")) {
16884             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16885             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16886             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16887             opt->value = n = 0;
16888             while(q = StrStr(q, " /// ")) {
16889                 n++; *q = 0;    // count choices, and null-terminate each of them
16890                 q += 5;
16891                 if(*q == '*') { // remember default, which is marked with * prefix
16892                     q++;
16893                     opt->value = n;
16894                 }
16895                 cps->comboList[cps->comboCnt++] = q;
16896             }
16897             cps->comboList[cps->comboCnt++] = NULL;
16898             opt->max = n + 1;
16899             opt->type = ComboBox;
16900         } else if(p = strstr(opt->name, " -button")) {
16901             opt->type = Button;
16902         } else if(p = strstr(opt->name, " -save")) {
16903             opt->type = SaveButton;
16904         } else return FALSE;
16905         *p = 0; // terminate option name
16906         // now look if the command-line options define a setting for this engine option.
16907         if(cps->optionSettings && cps->optionSettings[0])
16908             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16909         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16910           snprintf(buf, MSG_SIZ, "option %s", p);
16911                 if(p = strstr(buf, ",")) *p = 0;
16912                 if(q = strchr(buf, '=')) switch(opt->type) {
16913                     case ComboBox:
16914                         for(n=0; n<opt->max; n++)
16915                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16916                         break;
16917                     case TextBox:
16918                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16919                         break;
16920                     case Spin:
16921                     case CheckBox:
16922                         opt->value = atoi(q+1);
16923                     default:
16924                         break;
16925                 }
16926                 strcat(buf, "\n");
16927                 SendToProgram(buf, cps);
16928         }
16929         return TRUE;
16930 }
16931
16932 void
16933 FeatureDone (ChessProgramState *cps, int val)
16934 {
16935   DelayedEventCallback cb = GetDelayedEvent();
16936   if ((cb == InitBackEnd3 && cps == &first) ||
16937       (cb == SettingsMenuIfReady && cps == &second) ||
16938       (cb == LoadEngine) ||
16939       (cb == TwoMachinesEventIfReady)) {
16940     CancelDelayedEvent();
16941     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16942   }
16943   cps->initDone = val;
16944   if(val) cps->reload = FALSE;
16945 }
16946
16947 /* Parse feature command from engine */
16948 void
16949 ParseFeatures (char *args, ChessProgramState *cps)
16950 {
16951   char *p = args;
16952   char *q = NULL;
16953   int val;
16954   char buf[MSG_SIZ];
16955
16956   for (;;) {
16957     while (*p == ' ') p++;
16958     if (*p == NULLCHAR) return;
16959
16960     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16961     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16962     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16963     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16964     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16965     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16966     if (BoolFeature(&p, "reuse", &val, cps)) {
16967       /* Engine can disable reuse, but can't enable it if user said no */
16968       if (!val) cps->reuse = FALSE;
16969       continue;
16970     }
16971     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16972     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16973       if (gameMode == TwoMachinesPlay) {
16974         DisplayTwoMachinesTitle();
16975       } else {
16976         DisplayTitle("");
16977       }
16978       continue;
16979     }
16980     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16981     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16982     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16983     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16984     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16985     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16986     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16987     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16988     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16989     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16990     if (IntFeature(&p, "done", &val, cps)) {
16991       FeatureDone(cps, val);
16992       continue;
16993     }
16994     /* Added by Tord: */
16995     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16996     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16997     /* End of additions by Tord */
16998
16999     /* [HGM] added features: */
17000     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17001     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17002     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17003     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17004     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17005     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17006     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17007     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17008         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17009         FREE(cps->option[cps->nrOptions].name);
17010         cps->option[cps->nrOptions].name = q; q = NULL;
17011         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17012           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17013             SendToProgram(buf, cps);
17014             continue;
17015         }
17016         if(cps->nrOptions >= MAX_OPTIONS) {
17017             cps->nrOptions--;
17018             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17019             DisplayError(buf, 0);
17020         }
17021         continue;
17022     }
17023     /* End of additions by HGM */
17024
17025     /* unknown feature: complain and skip */
17026     q = p;
17027     while (*q && *q != '=') q++;
17028     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17029     SendToProgram(buf, cps);
17030     p = q;
17031     if (*p == '=') {
17032       p++;
17033       if (*p == '\"') {
17034         p++;
17035         while (*p && *p != '\"') p++;
17036         if (*p == '\"') p++;
17037       } else {
17038         while (*p && *p != ' ') p++;
17039       }
17040     }
17041   }
17042
17043 }
17044
17045 void
17046 PeriodicUpdatesEvent (int newState)
17047 {
17048     if (newState == appData.periodicUpdates)
17049       return;
17050
17051     appData.periodicUpdates=newState;
17052
17053     /* Display type changes, so update it now */
17054 //    DisplayAnalysis();
17055
17056     /* Get the ball rolling again... */
17057     if (newState) {
17058         AnalysisPeriodicEvent(1);
17059         StartAnalysisClock();
17060     }
17061 }
17062
17063 void
17064 PonderNextMoveEvent (int newState)
17065 {
17066     if (newState == appData.ponderNextMove) return;
17067     if (gameMode == EditPosition) EditPositionDone(TRUE);
17068     if (newState) {
17069         SendToProgram("hard\n", &first);
17070         if (gameMode == TwoMachinesPlay) {
17071             SendToProgram("hard\n", &second);
17072         }
17073     } else {
17074         SendToProgram("easy\n", &first);
17075         thinkOutput[0] = NULLCHAR;
17076         if (gameMode == TwoMachinesPlay) {
17077             SendToProgram("easy\n", &second);
17078         }
17079     }
17080     appData.ponderNextMove = newState;
17081 }
17082
17083 void
17084 NewSettingEvent (int option, int *feature, char *command, int value)
17085 {
17086     char buf[MSG_SIZ];
17087
17088     if (gameMode == EditPosition) EditPositionDone(TRUE);
17089     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17090     if(feature == NULL || *feature) SendToProgram(buf, &first);
17091     if (gameMode == TwoMachinesPlay) {
17092         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17093     }
17094 }
17095
17096 void
17097 ShowThinkingEvent ()
17098 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17099 {
17100     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17101     int newState = appData.showThinking
17102         // [HGM] thinking: other features now need thinking output as well
17103         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17104
17105     if (oldState == newState) return;
17106     oldState = newState;
17107     if (gameMode == EditPosition) EditPositionDone(TRUE);
17108     if (oldState) {
17109         SendToProgram("post\n", &first);
17110         if (gameMode == TwoMachinesPlay) {
17111             SendToProgram("post\n", &second);
17112         }
17113     } else {
17114         SendToProgram("nopost\n", &first);
17115         thinkOutput[0] = NULLCHAR;
17116         if (gameMode == TwoMachinesPlay) {
17117             SendToProgram("nopost\n", &second);
17118         }
17119     }
17120 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17121 }
17122
17123 void
17124 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17125 {
17126   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17127   if (pr == NoProc) return;
17128   AskQuestion(title, question, replyPrefix, pr);
17129 }
17130
17131 void
17132 TypeInEvent (char firstChar)
17133 {
17134     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17135         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17136         gameMode == AnalyzeMode || gameMode == EditGame ||
17137         gameMode == EditPosition || gameMode == IcsExamining ||
17138         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17139         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17140                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17141                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17142         gameMode == Training) PopUpMoveDialog(firstChar);
17143 }
17144
17145 void
17146 TypeInDoneEvent (char *move)
17147 {
17148         Board board;
17149         int n, fromX, fromY, toX, toY;
17150         char promoChar;
17151         ChessMove moveType;
17152
17153         // [HGM] FENedit
17154         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17155                 EditPositionPasteFEN(move);
17156                 return;
17157         }
17158         // [HGM] movenum: allow move number to be typed in any mode
17159         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17160           ToNrEvent(2*n-1);
17161           return;
17162         }
17163         // undocumented kludge: allow command-line option to be typed in!
17164         // (potentially fatal, and does not implement the effect of the option.)
17165         // should only be used for options that are values on which future decisions will be made,
17166         // and definitely not on options that would be used during initialization.
17167         if(strstr(move, "!!! -") == move) {
17168             ParseArgsFromString(move+4);
17169             return;
17170         }
17171
17172       if (gameMode != EditGame && currentMove != forwardMostMove &&
17173         gameMode != Training) {
17174         DisplayMoveError(_("Displayed move is not current"));
17175       } else {
17176         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17177           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17178         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17179         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17180           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17181           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17182         } else {
17183           DisplayMoveError(_("Could not parse move"));
17184         }
17185       }
17186 }
17187
17188 void
17189 DisplayMove (int moveNumber)
17190 {
17191     char message[MSG_SIZ];
17192     char res[MSG_SIZ];
17193     char cpThinkOutput[MSG_SIZ];
17194
17195     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17196
17197     if (moveNumber == forwardMostMove - 1 ||
17198         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17199
17200         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17201
17202         if (strchr(cpThinkOutput, '\n')) {
17203             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17204         }
17205     } else {
17206         *cpThinkOutput = NULLCHAR;
17207     }
17208
17209     /* [AS] Hide thinking from human user */
17210     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17211         *cpThinkOutput = NULLCHAR;
17212         if( thinkOutput[0] != NULLCHAR ) {
17213             int i;
17214
17215             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17216                 cpThinkOutput[i] = '.';
17217             }
17218             cpThinkOutput[i] = NULLCHAR;
17219             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17220         }
17221     }
17222
17223     if (moveNumber == forwardMostMove - 1 &&
17224         gameInfo.resultDetails != NULL) {
17225         if (gameInfo.resultDetails[0] == NULLCHAR) {
17226           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17227         } else {
17228           snprintf(res, MSG_SIZ, " {%s} %s",
17229                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17230         }
17231     } else {
17232         res[0] = NULLCHAR;
17233     }
17234
17235     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17236         DisplayMessage(res, cpThinkOutput);
17237     } else {
17238       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17239                 WhiteOnMove(moveNumber) ? " " : ".. ",
17240                 parseList[moveNumber], res);
17241         DisplayMessage(message, cpThinkOutput);
17242     }
17243 }
17244
17245 void
17246 DisplayComment (int moveNumber, char *text)
17247 {
17248     char title[MSG_SIZ];
17249
17250     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17251       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17252     } else {
17253       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17254               WhiteOnMove(moveNumber) ? " " : ".. ",
17255               parseList[moveNumber]);
17256     }
17257     if (text != NULL && (appData.autoDisplayComment || commentUp))
17258         CommentPopUp(title, text);
17259 }
17260
17261 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17262  * might be busy thinking or pondering.  It can be omitted if your
17263  * gnuchess is configured to stop thinking immediately on any user
17264  * input.  However, that gnuchess feature depends on the FIONREAD
17265  * ioctl, which does not work properly on some flavors of Unix.
17266  */
17267 void
17268 Attention (ChessProgramState *cps)
17269 {
17270 #if ATTENTION
17271     if (!cps->useSigint) return;
17272     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17273     switch (gameMode) {
17274       case MachinePlaysWhite:
17275       case MachinePlaysBlack:
17276       case TwoMachinesPlay:
17277       case IcsPlayingWhite:
17278       case IcsPlayingBlack:
17279       case AnalyzeMode:
17280       case AnalyzeFile:
17281         /* Skip if we know it isn't thinking */
17282         if (!cps->maybeThinking) return;
17283         if (appData.debugMode)
17284           fprintf(debugFP, "Interrupting %s\n", cps->which);
17285         InterruptChildProcess(cps->pr);
17286         cps->maybeThinking = FALSE;
17287         break;
17288       default:
17289         break;
17290     }
17291 #endif /*ATTENTION*/
17292 }
17293
17294 int
17295 CheckFlags ()
17296 {
17297     if (whiteTimeRemaining <= 0) {
17298         if (!whiteFlag) {
17299             whiteFlag = TRUE;
17300             if (appData.icsActive) {
17301                 if (appData.autoCallFlag &&
17302                     gameMode == IcsPlayingBlack && !blackFlag) {
17303                   SendToICS(ics_prefix);
17304                   SendToICS("flag\n");
17305                 }
17306             } else {
17307                 if (blackFlag) {
17308                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17309                 } else {
17310                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17311                     if (appData.autoCallFlag) {
17312                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17313                         return TRUE;
17314                     }
17315                 }
17316             }
17317         }
17318     }
17319     if (blackTimeRemaining <= 0) {
17320         if (!blackFlag) {
17321             blackFlag = TRUE;
17322             if (appData.icsActive) {
17323                 if (appData.autoCallFlag &&
17324                     gameMode == IcsPlayingWhite && !whiteFlag) {
17325                   SendToICS(ics_prefix);
17326                   SendToICS("flag\n");
17327                 }
17328             } else {
17329                 if (whiteFlag) {
17330                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17331                 } else {
17332                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17333                     if (appData.autoCallFlag) {
17334                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17335                         return TRUE;
17336                     }
17337                 }
17338             }
17339         }
17340     }
17341     return FALSE;
17342 }
17343
17344 void
17345 CheckTimeControl ()
17346 {
17347     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17348         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17349
17350     /*
17351      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17352      */
17353     if ( !WhiteOnMove(forwardMostMove) ) {
17354         /* White made time control */
17355         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17356         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17357         /* [HGM] time odds: correct new time quota for time odds! */
17358                                             / WhitePlayer()->timeOdds;
17359         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17360     } else {
17361         lastBlack -= blackTimeRemaining;
17362         /* Black made time control */
17363         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17364                                             / WhitePlayer()->other->timeOdds;
17365         lastWhite = whiteTimeRemaining;
17366     }
17367 }
17368
17369 void
17370 DisplayBothClocks ()
17371 {
17372     int wom = gameMode == EditPosition ?
17373       !blackPlaysFirst : WhiteOnMove(currentMove);
17374     DisplayWhiteClock(whiteTimeRemaining, wom);
17375     DisplayBlackClock(blackTimeRemaining, !wom);
17376 }
17377
17378
17379 /* Timekeeping seems to be a portability nightmare.  I think everyone
17380    has ftime(), but I'm really not sure, so I'm including some ifdefs
17381    to use other calls if you don't.  Clocks will be less accurate if
17382    you have neither ftime nor gettimeofday.
17383 */
17384
17385 /* VS 2008 requires the #include outside of the function */
17386 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17387 #include <sys/timeb.h>
17388 #endif
17389
17390 /* Get the current time as a TimeMark */
17391 void
17392 GetTimeMark (TimeMark *tm)
17393 {
17394 #if HAVE_GETTIMEOFDAY
17395
17396     struct timeval timeVal;
17397     struct timezone timeZone;
17398
17399     gettimeofday(&timeVal, &timeZone);
17400     tm->sec = (long) timeVal.tv_sec;
17401     tm->ms = (int) (timeVal.tv_usec / 1000L);
17402
17403 #else /*!HAVE_GETTIMEOFDAY*/
17404 #if HAVE_FTIME
17405
17406 // include <sys/timeb.h> / moved to just above start of function
17407     struct timeb timeB;
17408
17409     ftime(&timeB);
17410     tm->sec = (long) timeB.time;
17411     tm->ms = (int) timeB.millitm;
17412
17413 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17414     tm->sec = (long) time(NULL);
17415     tm->ms = 0;
17416 #endif
17417 #endif
17418 }
17419
17420 /* Return the difference in milliseconds between two
17421    time marks.  We assume the difference will fit in a long!
17422 */
17423 long
17424 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17425 {
17426     return 1000L*(tm2->sec - tm1->sec) +
17427            (long) (tm2->ms - tm1->ms);
17428 }
17429
17430
17431 /*
17432  * Code to manage the game clocks.
17433  *
17434  * In tournament play, black starts the clock and then white makes a move.
17435  * We give the human user a slight advantage if he is playing white---the
17436  * clocks don't run until he makes his first move, so it takes zero time.
17437  * Also, we don't account for network lag, so we could get out of sync
17438  * with GNU Chess's clock -- but then, referees are always right.
17439  */
17440
17441 static TimeMark tickStartTM;
17442 static long intendedTickLength;
17443
17444 long
17445 NextTickLength (long timeRemaining)
17446 {
17447     long nominalTickLength, nextTickLength;
17448
17449     if (timeRemaining > 0L && timeRemaining <= 10000L)
17450       nominalTickLength = 100L;
17451     else
17452       nominalTickLength = 1000L;
17453     nextTickLength = timeRemaining % nominalTickLength;
17454     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17455
17456     return nextTickLength;
17457 }
17458
17459 /* Adjust clock one minute up or down */
17460 void
17461 AdjustClock (Boolean which, int dir)
17462 {
17463     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17464     if(which) blackTimeRemaining += 60000*dir;
17465     else      whiteTimeRemaining += 60000*dir;
17466     DisplayBothClocks();
17467     adjustedClock = TRUE;
17468 }
17469
17470 /* Stop clocks and reset to a fresh time control */
17471 void
17472 ResetClocks ()
17473 {
17474     (void) StopClockTimer();
17475     if (appData.icsActive) {
17476         whiteTimeRemaining = blackTimeRemaining = 0;
17477     } else if (searchTime) {
17478         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17479         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17480     } else { /* [HGM] correct new time quote for time odds */
17481         whiteTC = blackTC = fullTimeControlString;
17482         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17483         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17484     }
17485     if (whiteFlag || blackFlag) {
17486         DisplayTitle("");
17487         whiteFlag = blackFlag = FALSE;
17488     }
17489     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17490     DisplayBothClocks();
17491     adjustedClock = FALSE;
17492 }
17493
17494 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17495
17496 /* Decrement running clock by amount of time that has passed */
17497 void
17498 DecrementClocks ()
17499 {
17500     long timeRemaining;
17501     long lastTickLength, fudge;
17502     TimeMark now;
17503
17504     if (!appData.clockMode) return;
17505     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17506
17507     GetTimeMark(&now);
17508
17509     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17510
17511     /* Fudge if we woke up a little too soon */
17512     fudge = intendedTickLength - lastTickLength;
17513     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17514
17515     if (WhiteOnMove(forwardMostMove)) {
17516         if(whiteNPS >= 0) lastTickLength = 0;
17517         timeRemaining = whiteTimeRemaining -= lastTickLength;
17518         if(timeRemaining < 0 && !appData.icsActive) {
17519             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17520             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17521                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17522                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17523             }
17524         }
17525         DisplayWhiteClock(whiteTimeRemaining - fudge,
17526                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17527     } else {
17528         if(blackNPS >= 0) lastTickLength = 0;
17529         timeRemaining = blackTimeRemaining -= lastTickLength;
17530         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17531             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17532             if(suddenDeath) {
17533                 blackStartMove = forwardMostMove;
17534                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17535             }
17536         }
17537         DisplayBlackClock(blackTimeRemaining - fudge,
17538                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17539     }
17540     if (CheckFlags()) return;
17541
17542     if(twoBoards) { // count down secondary board's clocks as well
17543         activePartnerTime -= lastTickLength;
17544         partnerUp = 1;
17545         if(activePartner == 'W')
17546             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17547         else
17548             DisplayBlackClock(activePartnerTime, TRUE);
17549         partnerUp = 0;
17550     }
17551
17552     tickStartTM = now;
17553     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17554     StartClockTimer(intendedTickLength);
17555
17556     /* if the time remaining has fallen below the alarm threshold, sound the
17557      * alarm. if the alarm has sounded and (due to a takeback or time control
17558      * with increment) the time remaining has increased to a level above the
17559      * threshold, reset the alarm so it can sound again.
17560      */
17561
17562     if (appData.icsActive && appData.icsAlarm) {
17563
17564         /* make sure we are dealing with the user's clock */
17565         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17566                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17567            )) return;
17568
17569         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17570             alarmSounded = FALSE;
17571         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17572             PlayAlarmSound();
17573             alarmSounded = TRUE;
17574         }
17575     }
17576 }
17577
17578
17579 /* A player has just moved, so stop the previously running
17580    clock and (if in clock mode) start the other one.
17581    We redisplay both clocks in case we're in ICS mode, because
17582    ICS gives us an update to both clocks after every move.
17583    Note that this routine is called *after* forwardMostMove
17584    is updated, so the last fractional tick must be subtracted
17585    from the color that is *not* on move now.
17586 */
17587 void
17588 SwitchClocks (int newMoveNr)
17589 {
17590     long lastTickLength;
17591     TimeMark now;
17592     int flagged = FALSE;
17593
17594     GetTimeMark(&now);
17595
17596     if (StopClockTimer() && appData.clockMode) {
17597         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17598         if (!WhiteOnMove(forwardMostMove)) {
17599             if(blackNPS >= 0) lastTickLength = 0;
17600             blackTimeRemaining -= lastTickLength;
17601            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17602 //         if(pvInfoList[forwardMostMove].time == -1)
17603                  pvInfoList[forwardMostMove].time =               // use GUI time
17604                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17605         } else {
17606            if(whiteNPS >= 0) lastTickLength = 0;
17607            whiteTimeRemaining -= lastTickLength;
17608            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17609 //         if(pvInfoList[forwardMostMove].time == -1)
17610                  pvInfoList[forwardMostMove].time =
17611                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17612         }
17613         flagged = CheckFlags();
17614     }
17615     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17616     CheckTimeControl();
17617
17618     if (flagged || !appData.clockMode) return;
17619
17620     switch (gameMode) {
17621       case MachinePlaysBlack:
17622       case MachinePlaysWhite:
17623       case BeginningOfGame:
17624         if (pausing) return;
17625         break;
17626
17627       case EditGame:
17628       case PlayFromGameFile:
17629       case IcsExamining:
17630         return;
17631
17632       default:
17633         break;
17634     }
17635
17636     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17637         if(WhiteOnMove(forwardMostMove))
17638              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17639         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17640     }
17641
17642     tickStartTM = now;
17643     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17644       whiteTimeRemaining : blackTimeRemaining);
17645     StartClockTimer(intendedTickLength);
17646 }
17647
17648
17649 /* Stop both clocks */
17650 void
17651 StopClocks ()
17652 {
17653     long lastTickLength;
17654     TimeMark now;
17655
17656     if (!StopClockTimer()) return;
17657     if (!appData.clockMode) return;
17658
17659     GetTimeMark(&now);
17660
17661     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17662     if (WhiteOnMove(forwardMostMove)) {
17663         if(whiteNPS >= 0) lastTickLength = 0;
17664         whiteTimeRemaining -= lastTickLength;
17665         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17666     } else {
17667         if(blackNPS >= 0) lastTickLength = 0;
17668         blackTimeRemaining -= lastTickLength;
17669         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17670     }
17671     CheckFlags();
17672 }
17673
17674 /* Start clock of player on move.  Time may have been reset, so
17675    if clock is already running, stop and restart it. */
17676 void
17677 StartClocks ()
17678 {
17679     (void) StopClockTimer(); /* in case it was running already */
17680     DisplayBothClocks();
17681     if (CheckFlags()) return;
17682
17683     if (!appData.clockMode) return;
17684     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17685
17686     GetTimeMark(&tickStartTM);
17687     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17688       whiteTimeRemaining : blackTimeRemaining);
17689
17690    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17691     whiteNPS = blackNPS = -1;
17692     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17693        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17694         whiteNPS = first.nps;
17695     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17696        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17697         blackNPS = first.nps;
17698     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17699         whiteNPS = second.nps;
17700     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17701         blackNPS = second.nps;
17702     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17703
17704     StartClockTimer(intendedTickLength);
17705 }
17706
17707 char *
17708 TimeString (long ms)
17709 {
17710     long second, minute, hour, day;
17711     char *sign = "";
17712     static char buf[32];
17713
17714     if (ms > 0 && ms <= 9900) {
17715       /* convert milliseconds to tenths, rounding up */
17716       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17717
17718       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17719       return buf;
17720     }
17721
17722     /* convert milliseconds to seconds, rounding up */
17723     /* use floating point to avoid strangeness of integer division
17724        with negative dividends on many machines */
17725     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17726
17727     if (second < 0) {
17728         sign = "-";
17729         second = -second;
17730     }
17731
17732     day = second / (60 * 60 * 24);
17733     second = second % (60 * 60 * 24);
17734     hour = second / (60 * 60);
17735     second = second % (60 * 60);
17736     minute = second / 60;
17737     second = second % 60;
17738
17739     if (day > 0)
17740       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17741               sign, day, hour, minute, second);
17742     else if (hour > 0)
17743       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17744     else
17745       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17746
17747     return buf;
17748 }
17749
17750
17751 /*
17752  * This is necessary because some C libraries aren't ANSI C compliant yet.
17753  */
17754 char *
17755 StrStr (char *string, char *match)
17756 {
17757     int i, length;
17758
17759     length = strlen(match);
17760
17761     for (i = strlen(string) - length; i >= 0; i--, string++)
17762       if (!strncmp(match, string, length))
17763         return string;
17764
17765     return NULL;
17766 }
17767
17768 char *
17769 StrCaseStr (char *string, char *match)
17770 {
17771     int i, j, length;
17772
17773     length = strlen(match);
17774
17775     for (i = strlen(string) - length; i >= 0; i--, string++) {
17776         for (j = 0; j < length; j++) {
17777             if (ToLower(match[j]) != ToLower(string[j]))
17778               break;
17779         }
17780         if (j == length) return string;
17781     }
17782
17783     return NULL;
17784 }
17785
17786 #ifndef _amigados
17787 int
17788 StrCaseCmp (char *s1, char *s2)
17789 {
17790     char c1, c2;
17791
17792     for (;;) {
17793         c1 = ToLower(*s1++);
17794         c2 = ToLower(*s2++);
17795         if (c1 > c2) return 1;
17796         if (c1 < c2) return -1;
17797         if (c1 == NULLCHAR) return 0;
17798     }
17799 }
17800
17801
17802 int
17803 ToLower (int c)
17804 {
17805     return isupper(c) ? tolower(c) : c;
17806 }
17807
17808
17809 int
17810 ToUpper (int c)
17811 {
17812     return islower(c) ? toupper(c) : c;
17813 }
17814 #endif /* !_amigados    */
17815
17816 char *
17817 StrSave (char *s)
17818 {
17819   char *ret;
17820
17821   if ((ret = (char *) malloc(strlen(s) + 1)))
17822     {
17823       safeStrCpy(ret, s, strlen(s)+1);
17824     }
17825   return ret;
17826 }
17827
17828 char *
17829 StrSavePtr (char *s, char **savePtr)
17830 {
17831     if (*savePtr) {
17832         free(*savePtr);
17833     }
17834     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17835       safeStrCpy(*savePtr, s, strlen(s)+1);
17836     }
17837     return(*savePtr);
17838 }
17839
17840 char *
17841 PGNDate ()
17842 {
17843     time_t clock;
17844     struct tm *tm;
17845     char buf[MSG_SIZ];
17846
17847     clock = time((time_t *)NULL);
17848     tm = localtime(&clock);
17849     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17850             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17851     return StrSave(buf);
17852 }
17853
17854
17855 char *
17856 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17857 {
17858     int i, j, fromX, fromY, toX, toY;
17859     int whiteToPlay;
17860     char buf[MSG_SIZ];
17861     char *p, *q;
17862     int emptycount;
17863     ChessSquare piece;
17864
17865     whiteToPlay = (gameMode == EditPosition) ?
17866       !blackPlaysFirst : (move % 2 == 0);
17867     p = buf;
17868
17869     /* Piece placement data */
17870     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17871         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17872         emptycount = 0;
17873         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17874             if (boards[move][i][j] == EmptySquare) {
17875                 emptycount++;
17876             } else { ChessSquare piece = boards[move][i][j];
17877                 if (emptycount > 0) {
17878                     if(emptycount<10) /* [HGM] can be >= 10 */
17879                         *p++ = '0' + emptycount;
17880                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17881                     emptycount = 0;
17882                 }
17883                 if(PieceToChar(piece) == '+') {
17884                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17885                     *p++ = '+';
17886                     piece = (ChessSquare)(CHUDEMOTED piece);
17887                 }
17888                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17889                 if(*p = PieceSuffix(piece)) p++;
17890                 if(p[-1] == '~') {
17891                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17892                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17893                     *p++ = '~';
17894                 }
17895             }
17896         }
17897         if (emptycount > 0) {
17898             if(emptycount<10) /* [HGM] can be >= 10 */
17899                 *p++ = '0' + emptycount;
17900             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17901             emptycount = 0;
17902         }
17903         *p++ = '/';
17904     }
17905     *(p - 1) = ' ';
17906
17907     /* [HGM] print Crazyhouse or Shogi holdings */
17908     if( gameInfo.holdingsWidth ) {
17909         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17910         q = p;
17911         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17912             piece = boards[move][i][BOARD_WIDTH-1];
17913             if( piece != EmptySquare )
17914               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17915                   *p++ = PieceToChar(piece);
17916         }
17917         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17918             piece = boards[move][BOARD_HEIGHT-i-1][0];
17919             if( piece != EmptySquare )
17920               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17921                   *p++ = PieceToChar(piece);
17922         }
17923
17924         if( q == p ) *p++ = '-';
17925         *p++ = ']';
17926         *p++ = ' ';
17927     }
17928
17929     /* Active color */
17930     *p++ = whiteToPlay ? 'w' : 'b';
17931     *p++ = ' ';
17932
17933   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17934     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17935   } else {
17936   if(nrCastlingRights) {
17937      int handW=0, handB=0;
17938      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17939         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17940         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17941      }
17942      q = p;
17943      if(appData.fischerCastling) {
17944         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17945            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17946                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17947         } else {
17948        /* [HGM] write directly from rights */
17949            if(boards[move][CASTLING][2] != NoRights &&
17950               boards[move][CASTLING][0] != NoRights   )
17951                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17952            if(boards[move][CASTLING][2] != NoRights &&
17953               boards[move][CASTLING][1] != NoRights   )
17954                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17955         }
17956         if(handB) {
17957            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17958                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17959         } else {
17960            if(boards[move][CASTLING][5] != NoRights &&
17961               boards[move][CASTLING][3] != NoRights   )
17962                 *p++ = boards[move][CASTLING][3] + AAA;
17963            if(boards[move][CASTLING][5] != NoRights &&
17964               boards[move][CASTLING][4] != NoRights   )
17965                 *p++ = boards[move][CASTLING][4] + AAA;
17966         }
17967      } else {
17968
17969         /* [HGM] write true castling rights */
17970         if( nrCastlingRights == 6 ) {
17971             int q, k=0;
17972             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17973                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17974             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17975                  boards[move][CASTLING][2] != NoRights  );
17976             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17977                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17978                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17979                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17980             }
17981             if(q) *p++ = 'Q';
17982             k = 0;
17983             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17984                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17985             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17986                  boards[move][CASTLING][5] != NoRights  );
17987             if(handB) {
17988                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17989                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17990                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17991             }
17992             if(q) *p++ = 'q';
17993         }
17994      }
17995      if (q == p) *p++ = '-'; /* No castling rights */
17996      *p++ = ' ';
17997   }
17998
17999   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18000      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18001      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18002     /* En passant target square */
18003     if (move > backwardMostMove) {
18004         fromX = moveList[move - 1][0] - AAA;
18005         fromY = moveList[move - 1][1] - ONE;
18006         toX = moveList[move - 1][2] - AAA;
18007         toY = moveList[move - 1][3] - ONE;
18008         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18009             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18010             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18011             fromX == toX) {
18012             /* 2-square pawn move just happened */
18013             *p++ = toX + AAA;
18014             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18015         } else {
18016             *p++ = '-';
18017         }
18018     } else if(move == backwardMostMove) {
18019         // [HGM] perhaps we should always do it like this, and forget the above?
18020         if((signed char)boards[move][EP_STATUS] >= 0) {
18021             *p++ = boards[move][EP_STATUS] + AAA;
18022             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18023         } else {
18024             *p++ = '-';
18025         }
18026     } else {
18027         *p++ = '-';
18028     }
18029     *p++ = ' ';
18030   }
18031   }
18032
18033     if(moveCounts)
18034     {   int i = 0, j=move;
18035
18036         /* [HGM] find reversible plies */
18037         if (appData.debugMode) { int k;
18038             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18039             for(k=backwardMostMove; k<=forwardMostMove; k++)
18040                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18041
18042         }
18043
18044         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18045         if( j == backwardMostMove ) i += initialRulePlies;
18046         sprintf(p, "%d ", i);
18047         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18048
18049         /* Fullmove number */
18050         sprintf(p, "%d", (move / 2) + 1);
18051     } else *--p = NULLCHAR;
18052
18053     return StrSave(buf);
18054 }
18055
18056 Boolean
18057 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18058 {
18059     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18060     char *p, c;
18061     int emptycount, virgin[BOARD_FILES];
18062     ChessSquare piece;
18063
18064     p = fen;
18065
18066     /* Piece placement data */
18067     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18068         j = 0;
18069         for (;;) {
18070             if (*p == '/' || *p == ' ' || *p == '[' ) {
18071                 if(j > w) w = j;
18072                 emptycount = gameInfo.boardWidth - j;
18073                 while (emptycount--)
18074                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18075                 if (*p == '/') p++;
18076                 else if(autoSize) { // we stumbled unexpectedly into end of board
18077                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18078                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18079                     }
18080                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18081                 }
18082                 break;
18083 #if(BOARD_FILES >= 10)*0
18084             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18085                 p++; emptycount=10;
18086                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18087                 while (emptycount--)
18088                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18089 #endif
18090             } else if (*p == '*') {
18091                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18092             } else if (isdigit(*p)) {
18093                 emptycount = *p++ - '0';
18094                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18095                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18096                 while (emptycount--)
18097                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18098             } else if (*p == '<') {
18099                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18100                 else if (i != 0 || !shuffle) return FALSE;
18101                 p++;
18102             } else if (shuffle && *p == '>') {
18103                 p++; // for now ignore closing shuffle range, and assume rank-end
18104             } else if (*p == '?') {
18105                 if (j >= gameInfo.boardWidth) return FALSE;
18106                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18107                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18108             } else if (*p == '+' || isalpha(*p)) {
18109                 char *q, *s = SUFFIXES;
18110                 if (j >= gameInfo.boardWidth) return FALSE;
18111                 if(*p=='+') {
18112                     char c = *++p;
18113                     if(q = strchr(s, p[1])) p++;
18114                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18115                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18116                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18117                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18118                 } else {
18119                     char c = *p++;
18120                     if(q = strchr(s, *p)) p++;
18121                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18122                 }
18123
18124                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18125                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18126                     piece = (ChessSquare) (PROMOTED piece);
18127                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18128                     p++;
18129                 }
18130                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18131                 if(piece == WhiteKing) wKingRank = i;
18132                 if(piece == BlackKing) bKingRank = i;
18133             } else {
18134                 return FALSE;
18135             }
18136         }
18137     }
18138     while (*p == '/' || *p == ' ') p++;
18139
18140     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18141
18142     /* [HGM] by default clear Crazyhouse holdings, if present */
18143     if(gameInfo.holdingsWidth) {
18144        for(i=0; i<BOARD_HEIGHT; i++) {
18145            board[i][0]             = EmptySquare; /* black holdings */
18146            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18147            board[i][1]             = (ChessSquare) 0; /* black counts */
18148            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18149        }
18150     }
18151
18152     /* [HGM] look for Crazyhouse holdings here */
18153     while(*p==' ') p++;
18154     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18155         int swap=0, wcnt=0, bcnt=0;
18156         if(*p == '[') p++;
18157         if(*p == '<') swap++, p++;
18158         if(*p == '-' ) p++; /* empty holdings */ else {
18159             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18160             /* if we would allow FEN reading to set board size, we would   */
18161             /* have to add holdings and shift the board read so far here   */
18162             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18163                 p++;
18164                 if((int) piece >= (int) BlackPawn ) {
18165                     i = (int)piece - (int)BlackPawn;
18166                     i = PieceToNumber((ChessSquare)i);
18167                     if( i >= gameInfo.holdingsSize ) return FALSE;
18168                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18169                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18170                     bcnt++;
18171                 } else {
18172                     i = (int)piece - (int)WhitePawn;
18173                     i = PieceToNumber((ChessSquare)i);
18174                     if( i >= gameInfo.holdingsSize ) return FALSE;
18175                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18176                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18177                     wcnt++;
18178                 }
18179             }
18180             if(subst) { // substitute back-rank question marks by holdings pieces
18181                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18182                     int k, m, n = bcnt + 1;
18183                     if(board[0][j] == ClearBoard) {
18184                         if(!wcnt) return FALSE;
18185                         n = rand() % wcnt;
18186                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18187                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18188                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18189                             break;
18190                         }
18191                     }
18192                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18193                         if(!bcnt) return FALSE;
18194                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18195                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18196                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18197                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18198                             break;
18199                         }
18200                     }
18201                 }
18202                 subst = 0;
18203             }
18204         }
18205         if(*p == ']') p++;
18206     }
18207
18208     if(subst) return FALSE; // substitution requested, but no holdings
18209
18210     while(*p == ' ') p++;
18211
18212     /* Active color */
18213     c = *p++;
18214     if(appData.colorNickNames) {
18215       if( c == appData.colorNickNames[0] ) c = 'w'; else
18216       if( c == appData.colorNickNames[1] ) c = 'b';
18217     }
18218     switch (c) {
18219       case 'w':
18220         *blackPlaysFirst = FALSE;
18221         break;
18222       case 'b':
18223         *blackPlaysFirst = TRUE;
18224         break;
18225       default:
18226         return FALSE;
18227     }
18228
18229     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18230     /* return the extra info in global variiables             */
18231
18232     while(*p==' ') p++;
18233
18234     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18235         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18236         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18237     }
18238
18239     /* set defaults in case FEN is incomplete */
18240     board[EP_STATUS] = EP_UNKNOWN;
18241     for(i=0; i<nrCastlingRights; i++ ) {
18242         board[CASTLING][i] =
18243             appData.fischerCastling ? NoRights : initialRights[i];
18244     }   /* assume possible unless obviously impossible */
18245     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18246     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18247     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18248                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18249     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18250     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18251     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18252                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18253     FENrulePlies = 0;
18254
18255     if(nrCastlingRights) {
18256       int fischer = 0;
18257       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18258       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18259           /* castling indicator present, so default becomes no castlings */
18260           for(i=0; i<nrCastlingRights; i++ ) {
18261                  board[CASTLING][i] = NoRights;
18262           }
18263       }
18264       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18265              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18266              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18267              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18268         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18269
18270         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18271             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18272             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18273         }
18274         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18275             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18276         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18277                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18278         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18279                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18280         switch(c) {
18281           case'K':
18282               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18283               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18284               board[CASTLING][2] = whiteKingFile;
18285               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18286               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18287               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18288               break;
18289           case'Q':
18290               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18291               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18292               board[CASTLING][2] = whiteKingFile;
18293               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18294               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18295               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18296               break;
18297           case'k':
18298               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18299               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18300               board[CASTLING][5] = blackKingFile;
18301               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18302               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18303               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18304               break;
18305           case'q':
18306               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18307               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18308               board[CASTLING][5] = blackKingFile;
18309               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18310               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18311               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18312           case '-':
18313               break;
18314           default: /* FRC castlings */
18315               if(c >= 'a') { /* black rights */
18316                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18317                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18318                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18319                   if(i == BOARD_RGHT) break;
18320                   board[CASTLING][5] = i;
18321                   c -= AAA;
18322                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18323                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18324                   if(c > i)
18325                       board[CASTLING][3] = c;
18326                   else
18327                       board[CASTLING][4] = c;
18328               } else { /* white rights */
18329                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18330                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18331                     if(board[0][i] == WhiteKing) break;
18332                   if(i == BOARD_RGHT) break;
18333                   board[CASTLING][2] = i;
18334                   c -= AAA - 'a' + 'A';
18335                   if(board[0][c] >= WhiteKing) break;
18336                   if(c > i)
18337                       board[CASTLING][0] = c;
18338                   else
18339                       board[CASTLING][1] = c;
18340               }
18341         }
18342       }
18343       for(i=0; i<nrCastlingRights; i++)
18344         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18345       if(gameInfo.variant == VariantSChess)
18346         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18347       if(fischer && shuffle) appData.fischerCastling = TRUE;
18348     if (appData.debugMode) {
18349         fprintf(debugFP, "FEN castling rights:");
18350         for(i=0; i<nrCastlingRights; i++)
18351         fprintf(debugFP, " %d", board[CASTLING][i]);
18352         fprintf(debugFP, "\n");
18353     }
18354
18355       while(*p==' ') p++;
18356     }
18357
18358     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18359
18360     /* read e.p. field in games that know e.p. capture */
18361     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18362        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18363        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18364       if(*p=='-') {
18365         p++; board[EP_STATUS] = EP_NONE;
18366       } else {
18367          char c = *p++ - AAA;
18368
18369          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18370          if(*p >= '0' && *p <='9') p++;
18371          board[EP_STATUS] = c;
18372       }
18373     }
18374
18375
18376     if(sscanf(p, "%d", &i) == 1) {
18377         FENrulePlies = i; /* 50-move ply counter */
18378         /* (The move number is still ignored)    */
18379     }
18380
18381     return TRUE;
18382 }
18383
18384 void
18385 EditPositionPasteFEN (char *fen)
18386 {
18387   if (fen != NULL) {
18388     Board initial_position;
18389
18390     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18391       DisplayError(_("Bad FEN position in clipboard"), 0);
18392       return ;
18393     } else {
18394       int savedBlackPlaysFirst = blackPlaysFirst;
18395       EditPositionEvent();
18396       blackPlaysFirst = savedBlackPlaysFirst;
18397       CopyBoard(boards[0], initial_position);
18398       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18399       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18400       DisplayBothClocks();
18401       DrawPosition(FALSE, boards[currentMove]);
18402     }
18403   }
18404 }
18405
18406 static char cseq[12] = "\\   ";
18407
18408 Boolean
18409 set_cont_sequence (char *new_seq)
18410 {
18411     int len;
18412     Boolean ret;
18413
18414     // handle bad attempts to set the sequence
18415         if (!new_seq)
18416                 return 0; // acceptable error - no debug
18417
18418     len = strlen(new_seq);
18419     ret = (len > 0) && (len < sizeof(cseq));
18420     if (ret)
18421       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18422     else if (appData.debugMode)
18423       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18424     return ret;
18425 }
18426
18427 /*
18428     reformat a source message so words don't cross the width boundary.  internal
18429     newlines are not removed.  returns the wrapped size (no null character unless
18430     included in source message).  If dest is NULL, only calculate the size required
18431     for the dest buffer.  lp argument indicats line position upon entry, and it's
18432     passed back upon exit.
18433 */
18434 int
18435 wrap (char *dest, char *src, int count, int width, int *lp)
18436 {
18437     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18438
18439     cseq_len = strlen(cseq);
18440     old_line = line = *lp;
18441     ansi = len = clen = 0;
18442
18443     for (i=0; i < count; i++)
18444     {
18445         if (src[i] == '\033')
18446             ansi = 1;
18447
18448         // if we hit the width, back up
18449         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18450         {
18451             // store i & len in case the word is too long
18452             old_i = i, old_len = len;
18453
18454             // find the end of the last word
18455             while (i && src[i] != ' ' && src[i] != '\n')
18456             {
18457                 i--;
18458                 len--;
18459             }
18460
18461             // word too long?  restore i & len before splitting it
18462             if ((old_i-i+clen) >= width)
18463             {
18464                 i = old_i;
18465                 len = old_len;
18466             }
18467
18468             // extra space?
18469             if (i && src[i-1] == ' ')
18470                 len--;
18471
18472             if (src[i] != ' ' && src[i] != '\n')
18473             {
18474                 i--;
18475                 if (len)
18476                     len--;
18477             }
18478
18479             // now append the newline and continuation sequence
18480             if (dest)
18481                 dest[len] = '\n';
18482             len++;
18483             if (dest)
18484                 strncpy(dest+len, cseq, cseq_len);
18485             len += cseq_len;
18486             line = cseq_len;
18487             clen = cseq_len;
18488             continue;
18489         }
18490
18491         if (dest)
18492             dest[len] = src[i];
18493         len++;
18494         if (!ansi)
18495             line++;
18496         if (src[i] == '\n')
18497             line = 0;
18498         if (src[i] == 'm')
18499             ansi = 0;
18500     }
18501     if (dest && appData.debugMode)
18502     {
18503         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18504             count, width, line, len, *lp);
18505         show_bytes(debugFP, src, count);
18506         fprintf(debugFP, "\ndest: ");
18507         show_bytes(debugFP, dest, len);
18508         fprintf(debugFP, "\n");
18509     }
18510     *lp = dest ? line : old_line;
18511
18512     return len;
18513 }
18514
18515 // [HGM] vari: routines for shelving variations
18516 Boolean modeRestore = FALSE;
18517
18518 void
18519 PushInner (int firstMove, int lastMove)
18520 {
18521         int i, j, nrMoves = lastMove - firstMove;
18522
18523         // push current tail of game on stack
18524         savedResult[storedGames] = gameInfo.result;
18525         savedDetails[storedGames] = gameInfo.resultDetails;
18526         gameInfo.resultDetails = NULL;
18527         savedFirst[storedGames] = firstMove;
18528         savedLast [storedGames] = lastMove;
18529         savedFramePtr[storedGames] = framePtr;
18530         framePtr -= nrMoves; // reserve space for the boards
18531         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18532             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18533             for(j=0; j<MOVE_LEN; j++)
18534                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18535             for(j=0; j<2*MOVE_LEN; j++)
18536                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18537             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18538             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18539             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18540             pvInfoList[firstMove+i-1].depth = 0;
18541             commentList[framePtr+i] = commentList[firstMove+i];
18542             commentList[firstMove+i] = NULL;
18543         }
18544
18545         storedGames++;
18546         forwardMostMove = firstMove; // truncate game so we can start variation
18547 }
18548
18549 void
18550 PushTail (int firstMove, int lastMove)
18551 {
18552         if(appData.icsActive) { // only in local mode
18553                 forwardMostMove = currentMove; // mimic old ICS behavior
18554                 return;
18555         }
18556         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18557
18558         PushInner(firstMove, lastMove);
18559         if(storedGames == 1) GreyRevert(FALSE);
18560         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18561 }
18562
18563 void
18564 PopInner (Boolean annotate)
18565 {
18566         int i, j, nrMoves;
18567         char buf[8000], moveBuf[20];
18568
18569         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18570         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18571         nrMoves = savedLast[storedGames] - currentMove;
18572         if(annotate) {
18573                 int cnt = 10;
18574                 if(!WhiteOnMove(currentMove))
18575                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18576                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18577                 for(i=currentMove; i<forwardMostMove; i++) {
18578                         if(WhiteOnMove(i))
18579                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18580                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18581                         strcat(buf, moveBuf);
18582                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18583                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18584                 }
18585                 strcat(buf, ")");
18586         }
18587         for(i=1; i<=nrMoves; i++) { // copy last variation back
18588             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18589             for(j=0; j<MOVE_LEN; j++)
18590                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18591             for(j=0; j<2*MOVE_LEN; j++)
18592                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18593             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18594             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18595             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18596             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18597             commentList[currentMove+i] = commentList[framePtr+i];
18598             commentList[framePtr+i] = NULL;
18599         }
18600         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18601         framePtr = savedFramePtr[storedGames];
18602         gameInfo.result = savedResult[storedGames];
18603         if(gameInfo.resultDetails != NULL) {
18604             free(gameInfo.resultDetails);
18605       }
18606         gameInfo.resultDetails = savedDetails[storedGames];
18607         forwardMostMove = currentMove + nrMoves;
18608 }
18609
18610 Boolean
18611 PopTail (Boolean annotate)
18612 {
18613         if(appData.icsActive) return FALSE; // only in local mode
18614         if(!storedGames) return FALSE; // sanity
18615         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18616
18617         PopInner(annotate);
18618         if(currentMove < forwardMostMove) ForwardEvent(); else
18619         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18620
18621         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18622         return TRUE;
18623 }
18624
18625 void
18626 CleanupTail ()
18627 {       // remove all shelved variations
18628         int i;
18629         for(i=0; i<storedGames; i++) {
18630             if(savedDetails[i])
18631                 free(savedDetails[i]);
18632             savedDetails[i] = NULL;
18633         }
18634         for(i=framePtr; i<MAX_MOVES; i++) {
18635                 if(commentList[i]) free(commentList[i]);
18636                 commentList[i] = NULL;
18637         }
18638         framePtr = MAX_MOVES-1;
18639         storedGames = 0;
18640 }
18641
18642 void
18643 LoadVariation (int index, char *text)
18644 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18645         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18646         int level = 0, move;
18647
18648         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18649         // first find outermost bracketing variation
18650         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18651             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18652                 if(*p == '{') wait = '}'; else
18653                 if(*p == '[') wait = ']'; else
18654                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18655                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18656             }
18657             if(*p == wait) wait = NULLCHAR; // closing ]} found
18658             p++;
18659         }
18660         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18661         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18662         end[1] = NULLCHAR; // clip off comment beyond variation
18663         ToNrEvent(currentMove-1);
18664         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18665         // kludge: use ParsePV() to append variation to game
18666         move = currentMove;
18667         ParsePV(start, TRUE, TRUE);
18668         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18669         ClearPremoveHighlights();
18670         CommentPopDown();
18671         ToNrEvent(currentMove+1);
18672 }
18673
18674 void
18675 LoadTheme ()
18676 {
18677     char *p, *q, buf[MSG_SIZ];
18678     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18679         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18680         ParseArgsFromString(buf);
18681         ActivateTheme(TRUE); // also redo colors
18682         return;
18683     }
18684     p = nickName;
18685     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18686     {
18687         int len;
18688         q = appData.themeNames;
18689         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18690       if(appData.useBitmaps) {
18691         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18692                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18693                 appData.liteBackTextureMode,
18694                 appData.darkBackTextureMode );
18695       } else {
18696         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18697                 Col2Text(2),   // lightSquareColor
18698                 Col2Text(3) ); // darkSquareColor
18699       }
18700       if(appData.useBorder) {
18701         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18702                 appData.border);
18703       } else {
18704         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18705       }
18706       if(appData.useFont) {
18707         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18708                 appData.renderPiecesWithFont,
18709                 appData.fontToPieceTable,
18710                 Col2Text(9),    // appData.fontBackColorWhite
18711                 Col2Text(10) ); // appData.fontForeColorBlack
18712       } else {
18713         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18714                 appData.pieceDirectory);
18715         if(!appData.pieceDirectory[0])
18716           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18717                 Col2Text(0),   // whitePieceColor
18718                 Col2Text(1) ); // blackPieceColor
18719       }
18720       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18721                 Col2Text(4),   // highlightSquareColor
18722                 Col2Text(5) ); // premoveHighlightColor
18723         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18724         if(insert != q) insert[-1] = NULLCHAR;
18725         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18726         if(q)   free(q);
18727     }
18728     ActivateTheme(FALSE);
18729 }