updated copyright for 2016
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void StartChessProgram P((ChessProgramState *cps));
197 void SendToProgram P((char *message, ChessProgramState *cps));
198 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
199 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
200                            char *buf, int count, int error));
201 void SendTimeControl P((ChessProgramState *cps,
202                         int mps, long tc, int inc, int sd, int st));
203 char *TimeControlTagValue P((void));
204 void Attention P((ChessProgramState *cps));
205 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
206 int ResurrectChessProgram P((void));
207 void DisplayComment P((int moveNumber, char *text));
208 void DisplayMove P((int moveNumber));
209
210 void ParseGameHistory P((char *game));
211 void ParseBoard12 P((char *string));
212 void KeepAlive P((void));
213 void StartClocks P((void));
214 void SwitchClocks P((int nr));
215 void StopClocks P((void));
216 void ResetClocks P((void));
217 char *PGNDate P((void));
218 void SetGameInfo P((void));
219 int RegisterMove P((void));
220 void MakeRegisteredMove P((void));
221 void TruncateGame P((void));
222 int looking_at P((char *, int *, char *));
223 void CopyPlayerNameIntoFileName P((char **, char *));
224 char *SavePart P((char *));
225 int SaveGameOldStyle P((FILE *));
226 int SaveGamePGN P((FILE *));
227 int CheckFlags P((void));
228 long NextTickLength P((long));
229 void CheckTimeControl P((void));
230 void show_bytes P((FILE *, char *, int));
231 int string_to_rating P((char *str));
232 void ParseFeatures P((char* args, ChessProgramState *cps));
233 void InitBackEnd3 P((void));
234 void FeatureDone P((ChessProgramState* cps, int val));
235 void InitChessProgram P((ChessProgramState *cps, int setup));
236 void OutputKibitz(int window, char *text);
237 int PerpetualChase(int first, int last);
238 int EngineOutputIsUp();
239 void InitDrawingSizes(int x, int y);
240 void NextMatchGame P((void));
241 int NextTourneyGame P((int nr, int *swap));
242 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
243 FILE *WriteTourneyFile P((char *results, FILE *f));
244 void DisplayTwoMachinesTitle P(());
245 static void ExcludeClick P((int index));
246 void ToggleSecond P((void));
247 void PauseEngine P((ChessProgramState *cps));
248 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
249
250 #ifdef WIN32
251        extern void ConsoleCreate();
252 #endif
253
254 ChessProgramState *WhitePlayer();
255 int VerifyDisplayMode P(());
256
257 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
258 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
259 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
260 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
261 void ics_update_width P((int new_width));
262 extern char installDir[MSG_SIZ];
263 VariantClass startVariant; /* [HGM] nicks: initial variant */
264 Boolean abortMatch;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445
446 ChessProgramState first, second, pairing;
447
448 /* premove variables */
449 int premoveToX = 0;
450 int premoveToY = 0;
451 int premoveFromX = 0;
452 int premoveFromY = 0;
453 int premovePromoChar = 0;
454 int gotPremove = 0;
455 Boolean alarmSounded;
456 /* end premove variables */
457
458 char *ics_prefix = "$";
459 enum ICS_TYPE ics_type = ICS_GENERIC;
460
461 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
462 int pauseExamForwardMostMove = 0;
463 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
464 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
465 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
466 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
467 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
468 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
469 int whiteFlag = FALSE, blackFlag = FALSE;
470 int userOfferedDraw = FALSE;
471 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
472 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
473 int cmailMoveType[CMAIL_MAX_GAMES];
474 long ics_clock_paused = 0;
475 ProcRef icsPR = NoProc, cmailPR = NoProc;
476 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
477 GameMode gameMode = BeginningOfGame;
478 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
479 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
480 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
481 int hiddenThinkOutputState = 0; /* [AS] */
482 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
483 int adjudicateLossPlies = 6;
484 char white_holding[64], black_holding[64];
485 TimeMark lastNodeCountTime;
486 long lastNodeCount=0;
487 int shiftKey, controlKey; // [HGM] set by mouse handler
488
489 int have_sent_ICS_logon = 0;
490 int movesPerSession;
491 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
492 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
493 Boolean adjustedClock;
494 long timeControl_2; /* [AS] Allow separate time controls */
495 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
496 long timeRemaining[2][MAX_MOVES];
497 int matchGame = 0, nextGame = 0, roundNr = 0;
498 Boolean waitingForGame = FALSE, startingEngine = FALSE;
499 TimeMark programStartTime, pauseStart;
500 char ics_handle[MSG_SIZ];
501 int have_set_title = 0;
502
503 /* animateTraining preserves the state of appData.animate
504  * when Training mode is activated. This allows the
505  * response to be animated when appData.animate == TRUE and
506  * appData.animateDragging == TRUE.
507  */
508 Boolean animateTraining;
509
510 GameInfo gameInfo;
511
512 AppData appData;
513
514 Board boards[MAX_MOVES];
515 /* [HGM] Following 7 needed for accurate legality tests: */
516 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
517 signed char  initialRights[BOARD_FILES];
518 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
519 int   initialRulePlies, FENrulePlies;
520 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
521 int loadFlag = 0;
522 Boolean shuffleOpenings;
523 int mute; // mute all sounds
524
525 // [HGM] vari: next 12 to save and restore variations
526 #define MAX_VARIATIONS 10
527 int framePtr = MAX_MOVES-1; // points to free stack entry
528 int storedGames = 0;
529 int savedFirst[MAX_VARIATIONS];
530 int savedLast[MAX_VARIATIONS];
531 int savedFramePtr[MAX_VARIATIONS];
532 char *savedDetails[MAX_VARIATIONS];
533 ChessMove savedResult[MAX_VARIATIONS];
534
535 void PushTail P((int firstMove, int lastMove));
536 Boolean PopTail P((Boolean annotate));
537 void PushInner P((int firstMove, int lastMove));
538 void PopInner P((Boolean annotate));
539 void CleanupTail P((void));
540
541 ChessSquare  FIDEArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackBishop, BlackKnight, BlackRook }
546 };
547
548 ChessSquare twoKingsArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
550         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
552         BlackKing, BlackKing, BlackKnight, BlackRook }
553 };
554
555 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
557         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
558     { BlackRook, BlackMan, BlackBishop, BlackQueen,
559         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
560 };
561
562 ChessSquare SpartanArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
566         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
567 };
568
569 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
573         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
574 };
575
576 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
578         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
580         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 };
582
583 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
591     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
592         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
593     { BlackRook, BlackKnight, BlackMan, BlackFerz,
594         BlackKing, BlackMan, BlackKnight, BlackRook }
595 };
596
597 ChessSquare  lionArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
599         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
600     { BlackRook, BlackLion, BlackBishop, BlackQueen,
601         BlackKing, BlackBishop, BlackKnight, BlackRook }
602 };
603
604
605 #if (BOARD_FILES>=10)
606 ChessSquare ShogiArray[2][BOARD_FILES] = {
607     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
608         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
609     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
610         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
611 };
612
613 ChessSquare XiangqiArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
615         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
617         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
618 };
619
620 ChessSquare CapablancaArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
622         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
624         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
625 };
626
627 ChessSquare GreatArray[2][BOARD_FILES] = {
628     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
629         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
630     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
631         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
632 };
633
634 ChessSquare JanusArray[2][BOARD_FILES] = {
635     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
636         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
637     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
638         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
639 };
640
641 ChessSquare GrandArray[2][BOARD_FILES] = {
642     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
643         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
644     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
645         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
646 };
647
648 ChessSquare ChuChessArray[2][BOARD_FILES] = {
649     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
650         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
651     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
652         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
653 };
654
655 #ifdef GOTHIC
656 ChessSquare GothicArray[2][BOARD_FILES] = {
657     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
658         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
659     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
660         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
661 };
662 #else // !GOTHIC
663 #define GothicArray CapablancaArray
664 #endif // !GOTHIC
665
666 #ifdef FALCON
667 ChessSquare FalconArray[2][BOARD_FILES] = {
668     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
669         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
670     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
671         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
672 };
673 #else // !FALCON
674 #define FalconArray CapablancaArray
675 #endif // !FALCON
676
677 #else // !(BOARD_FILES>=10)
678 #define XiangqiPosition FIDEArray
679 #define CapablancaArray FIDEArray
680 #define GothicArray FIDEArray
681 #define GreatArray FIDEArray
682 #endif // !(BOARD_FILES>=10)
683
684 #if (BOARD_FILES>=12)
685 ChessSquare CourierArray[2][BOARD_FILES] = {
686     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
687         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
688     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
689         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
690 };
691 ChessSquare ChuArray[6][BOARD_FILES] = {
692     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
693       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
694     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
695       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
696     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
697       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
698     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
699       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
700     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
701       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
702     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
703       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
704 };
705 #else // !(BOARD_FILES>=12)
706 #define CourierArray CapablancaArray
707 #define ChuArray CapablancaArray
708 #endif // !(BOARD_FILES>=12)
709
710
711 Board initialPosition;
712
713
714 /* Convert str to a rating. Checks for special cases of "----",
715
716    "++++", etc. Also strips ()'s */
717 int
718 string_to_rating (char *str)
719 {
720   while(*str && !isdigit(*str)) ++str;
721   if (!*str)
722     return 0;   /* One of the special "no rating" cases */
723   else
724     return atoi(str);
725 }
726
727 void
728 ClearProgramStats ()
729 {
730     /* Init programStats */
731     programStats.movelist[0] = 0;
732     programStats.depth = 0;
733     programStats.nr_moves = 0;
734     programStats.moves_left = 0;
735     programStats.nodes = 0;
736     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
737     programStats.score = 0;
738     programStats.got_only_move = 0;
739     programStats.got_fail = 0;
740     programStats.line_is_book = 0;
741 }
742
743 void
744 CommonEngineInit ()
745 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
746     if (appData.firstPlaysBlack) {
747         first.twoMachinesColor = "black\n";
748         second.twoMachinesColor = "white\n";
749     } else {
750         first.twoMachinesColor = "white\n";
751         second.twoMachinesColor = "black\n";
752     }
753
754     first.other = &second;
755     second.other = &first;
756
757     { float norm = 1;
758         if(appData.timeOddsMode) {
759             norm = appData.timeOdds[0];
760             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
761         }
762         first.timeOdds  = appData.timeOdds[0]/norm;
763         second.timeOdds = appData.timeOdds[1]/norm;
764     }
765
766     if(programVersion) free(programVersion);
767     if (appData.noChessProgram) {
768         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
769         sprintf(programVersion, "%s", PACKAGE_STRING);
770     } else {
771       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
772       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
773       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
774     }
775 }
776
777 void
778 UnloadEngine (ChessProgramState *cps)
779 {
780         /* Kill off first chess program */
781         if (cps->isr != NULL)
782           RemoveInputSource(cps->isr);
783         cps->isr = NULL;
784
785         if (cps->pr != NoProc) {
786             ExitAnalyzeMode();
787             DoSleep( appData.delayBeforeQuit );
788             SendToProgram("quit\n", cps);
789             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
790         }
791         cps->pr = NoProc;
792         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
793 }
794
795 void
796 ClearOptions (ChessProgramState *cps)
797 {
798     int i;
799     cps->nrOptions = cps->comboCnt = 0;
800     for(i=0; i<MAX_OPTIONS; i++) {
801         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
802         cps->option[i].textValue = 0;
803     }
804 }
805
806 char *engineNames[] = {
807   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
808      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
809 N_("first"),
810   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("second")
813 };
814
815 void
816 InitEngine (ChessProgramState *cps, int n)
817 {   // [HGM] all engine initialiation put in a function that does one engine
818
819     ClearOptions(cps);
820
821     cps->which = engineNames[n];
822     cps->maybeThinking = FALSE;
823     cps->pr = NoProc;
824     cps->isr = NULL;
825     cps->sendTime = 2;
826     cps->sendDrawOffers = 1;
827
828     cps->program = appData.chessProgram[n];
829     cps->host = appData.host[n];
830     cps->dir = appData.directory[n];
831     cps->initString = appData.engInitString[n];
832     cps->computerString = appData.computerString[n];
833     cps->useSigint  = TRUE;
834     cps->useSigterm = TRUE;
835     cps->reuse = appData.reuse[n];
836     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
837     cps->useSetboard = FALSE;
838     cps->useSAN = FALSE;
839     cps->usePing = FALSE;
840     cps->lastPing = 0;
841     cps->lastPong = 0;
842     cps->usePlayother = FALSE;
843     cps->useColors = TRUE;
844     cps->useUsermove = FALSE;
845     cps->sendICS = FALSE;
846     cps->sendName = appData.icsActive;
847     cps->sdKludge = FALSE;
848     cps->stKludge = FALSE;
849     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
850     TidyProgramName(cps->program, cps->host, cps->tidy);
851     cps->matchWins = 0;
852     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
853     cps->analysisSupport = 2; /* detect */
854     cps->analyzing = FALSE;
855     cps->initDone = FALSE;
856     cps->reload = FALSE;
857     cps->pseudo = appData.pseudo[n];
858
859     /* New features added by Tord: */
860     cps->useFEN960 = FALSE;
861     cps->useOOCastle = TRUE;
862     /* End of new features added by Tord. */
863     cps->fenOverride  = appData.fenOverride[n];
864
865     /* [HGM] time odds: set factor for each machine */
866     cps->timeOdds  = appData.timeOdds[n];
867
868     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
869     cps->accumulateTC = appData.accumulateTC[n];
870     cps->maxNrOfSessions = 1;
871
872     /* [HGM] debug */
873     cps->debug = FALSE;
874
875     cps->drawDepth = appData.drawDepth[n];
876     cps->supportsNPS = UNKNOWN;
877     cps->memSize = FALSE;
878     cps->maxCores = FALSE;
879     ASSIGN(cps->egtFormats, "");
880
881     /* [HGM] options */
882     cps->optionSettings  = appData.engOptions[n];
883
884     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
885     cps->isUCI = appData.isUCI[n]; /* [AS] */
886     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
887     cps->highlight = 0;
888
889     if (appData.protocolVersion[n] > PROTOVER
890         || appData.protocolVersion[n] < 1)
891       {
892         char buf[MSG_SIZ];
893         int len;
894
895         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
896                        appData.protocolVersion[n]);
897         if( (len >= MSG_SIZ) && appData.debugMode )
898           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
899
900         DisplayFatalError(buf, 0, 2);
901       }
902     else
903       {
904         cps->protocolVersion = appData.protocolVersion[n];
905       }
906
907     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
908     ParseFeatures(appData.featureDefaults, cps);
909 }
910
911 ChessProgramState *savCps;
912
913 GameMode oldMode;
914
915 void
916 LoadEngine ()
917 {
918     int i;
919     if(WaitForEngine(savCps, LoadEngine)) return;
920     CommonEngineInit(); // recalculate time odds
921     if(gameInfo.variant != StringToVariant(appData.variant)) {
922         // we changed variant when loading the engine; this forces us to reset
923         Reset(TRUE, savCps != &first);
924         oldMode = BeginningOfGame; // to prevent restoring old mode
925     }
926     InitChessProgram(savCps, FALSE);
927     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
928     DisplayMessage("", "");
929     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
930     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
931     ThawUI();
932     SetGNUMode();
933     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
934 }
935
936 void
937 ReplaceEngine (ChessProgramState *cps, int n)
938 {
939     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
940     keepInfo = 1;
941     if(oldMode != BeginningOfGame) EditGameEvent();
942     keepInfo = 0;
943     UnloadEngine(cps);
944     appData.noChessProgram = FALSE;
945     appData.clockMode = TRUE;
946     InitEngine(cps, n);
947     UpdateLogos(TRUE);
948     if(n) return; // only startup first engine immediately; second can wait
949     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
950     LoadEngine();
951 }
952
953 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
954 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
955
956 static char resetOptions[] =
957         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
958         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
959         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
960         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
961
962 void
963 FloatToFront(char **list, char *engineLine)
964 {
965     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
966     int i=0;
967     if(appData.recentEngines <= 0) return;
968     TidyProgramName(engineLine, "localhost", tidy+1);
969     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
970     strncpy(buf+1, *list, MSG_SIZ-50);
971     if(p = strstr(buf, tidy)) { // tidy name appears in list
972         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
973         while(*p++ = *++q); // squeeze out
974     }
975     strcat(tidy, buf+1); // put list behind tidy name
976     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
977     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
978     ASSIGN(*list, tidy+1);
979 }
980
981 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
982
983 void
984 Load (ChessProgramState *cps, int i)
985 {
986     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
987     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
988         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
989         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
990         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
991         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
992         appData.firstProtocolVersion = PROTOVER;
993         ParseArgsFromString(buf);
994         SwapEngines(i);
995         ReplaceEngine(cps, i);
996         FloatToFront(&appData.recentEngineList, engineLine);
997         return;
998     }
999     p = engineName;
1000     while(q = strchr(p, SLASH)) p = q+1;
1001     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1002     if(engineDir[0] != NULLCHAR) {
1003         ASSIGN(appData.directory[i], engineDir); p = engineName;
1004     } else if(p != engineName) { // derive directory from engine path, when not given
1005         p[-1] = 0;
1006         ASSIGN(appData.directory[i], engineName);
1007         p[-1] = SLASH;
1008         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1009     } else { ASSIGN(appData.directory[i], "."); }
1010     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1011     if(params[0]) {
1012         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1013         snprintf(command, MSG_SIZ, "%s %s", p, params);
1014         p = command;
1015     }
1016     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1017     ASSIGN(appData.chessProgram[i], p);
1018     appData.isUCI[i] = isUCI;
1019     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1020     appData.hasOwnBookUCI[i] = hasBook;
1021     if(!nickName[0]) useNick = FALSE;
1022     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1023     if(addToList) {
1024         int len;
1025         char quote;
1026         q = firstChessProgramNames;
1027         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1028         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1029         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1030                         quote, p, quote, appData.directory[i],
1031                         useNick ? " -fn \"" : "",
1032                         useNick ? nickName : "",
1033                         useNick ? "\"" : "",
1034                         v1 ? " -firstProtocolVersion 1" : "",
1035                         hasBook ? "" : " -fNoOwnBookUCI",
1036                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1037                         storeVariant ? " -variant " : "",
1038                         storeVariant ? VariantName(gameInfo.variant) : "");
1039         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1040         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1041         if(insert != q) insert[-1] = NULLCHAR;
1042         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1043         if(q)   free(q);
1044         FloatToFront(&appData.recentEngineList, buf);
1045     }
1046     ReplaceEngine(cps, i);
1047 }
1048
1049 void
1050 InitTimeControls ()
1051 {
1052     int matched, min, sec;
1053     /*
1054      * Parse timeControl resource
1055      */
1056     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1057                           appData.movesPerSession)) {
1058         char buf[MSG_SIZ];
1059         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1060         DisplayFatalError(buf, 0, 2);
1061     }
1062
1063     /*
1064      * Parse searchTime resource
1065      */
1066     if (*appData.searchTime != NULLCHAR) {
1067         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1068         if (matched == 1) {
1069             searchTime = min * 60;
1070         } else if (matched == 2) {
1071             searchTime = min * 60 + sec;
1072         } else {
1073             char buf[MSG_SIZ];
1074             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1075             DisplayFatalError(buf, 0, 2);
1076         }
1077     }
1078 }
1079
1080 void
1081 InitBackEnd1 ()
1082 {
1083
1084     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1085     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1086
1087     GetTimeMark(&programStartTime);
1088     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1089     appData.seedBase = random() + (random()<<15);
1090     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1091
1092     ClearProgramStats();
1093     programStats.ok_to_send = 1;
1094     programStats.seen_stat = 0;
1095
1096     /*
1097      * Initialize game list
1098      */
1099     ListNew(&gameList);
1100
1101
1102     /*
1103      * Internet chess server status
1104      */
1105     if (appData.icsActive) {
1106         appData.matchMode = FALSE;
1107         appData.matchGames = 0;
1108 #if ZIPPY
1109         appData.noChessProgram = !appData.zippyPlay;
1110 #else
1111         appData.zippyPlay = FALSE;
1112         appData.zippyTalk = FALSE;
1113         appData.noChessProgram = TRUE;
1114 #endif
1115         if (*appData.icsHelper != NULLCHAR) {
1116             appData.useTelnet = TRUE;
1117             appData.telnetProgram = appData.icsHelper;
1118         }
1119     } else {
1120         appData.zippyTalk = appData.zippyPlay = FALSE;
1121     }
1122
1123     /* [AS] Initialize pv info list [HGM] and game state */
1124     {
1125         int i, j;
1126
1127         for( i=0; i<=framePtr; i++ ) {
1128             pvInfoList[i].depth = -1;
1129             boards[i][EP_STATUS] = EP_NONE;
1130             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1131         }
1132     }
1133
1134     InitTimeControls();
1135
1136     /* [AS] Adjudication threshold */
1137     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1138
1139     InitEngine(&first, 0);
1140     InitEngine(&second, 1);
1141     CommonEngineInit();
1142
1143     pairing.which = "pairing"; // pairing engine
1144     pairing.pr = NoProc;
1145     pairing.isr = NULL;
1146     pairing.program = appData.pairingEngine;
1147     pairing.host = "localhost";
1148     pairing.dir = ".";
1149
1150     if (appData.icsActive) {
1151         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1152     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1153         appData.clockMode = FALSE;
1154         first.sendTime = second.sendTime = 0;
1155     }
1156
1157 #if ZIPPY
1158     /* Override some settings from environment variables, for backward
1159        compatibility.  Unfortunately it's not feasible to have the env
1160        vars just set defaults, at least in xboard.  Ugh.
1161     */
1162     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1163       ZippyInit();
1164     }
1165 #endif
1166
1167     if (!appData.icsActive) {
1168       char buf[MSG_SIZ];
1169       int len;
1170
1171       /* Check for variants that are supported only in ICS mode,
1172          or not at all.  Some that are accepted here nevertheless
1173          have bugs; see comments below.
1174       */
1175       VariantClass variant = StringToVariant(appData.variant);
1176       switch (variant) {
1177       case VariantBughouse:     /* need four players and two boards */
1178       case VariantKriegspiel:   /* need to hide pieces and move details */
1179         /* case VariantFischeRandom: (Fabien: moved below) */
1180         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1181         if( (len >= MSG_SIZ) && appData.debugMode )
1182           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1183
1184         DisplayFatalError(buf, 0, 2);
1185         return;
1186
1187       case VariantUnknown:
1188       case VariantLoadable:
1189       case Variant29:
1190       case Variant30:
1191       case Variant31:
1192       case Variant32:
1193       case Variant33:
1194       case Variant34:
1195       case Variant35:
1196       case Variant36:
1197       default:
1198         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1199         if( (len >= MSG_SIZ) && appData.debugMode )
1200           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1201
1202         DisplayFatalError(buf, 0, 2);
1203         return;
1204
1205       case VariantNormal:     /* definitely works! */
1206         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1207           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1208           return;
1209         }
1210       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1211       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1212       case VariantGothic:     /* [HGM] should work */
1213       case VariantCapablanca: /* [HGM] should work */
1214       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1215       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1216       case VariantChu:        /* [HGM] experimental */
1217       case VariantKnightmate: /* [HGM] should work */
1218       case VariantCylinder:   /* [HGM] untested */
1219       case VariantFalcon:     /* [HGM] untested */
1220       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1221                                  offboard interposition not understood */
1222       case VariantWildCastle: /* pieces not automatically shuffled */
1223       case VariantNoCastle:   /* pieces not automatically shuffled */
1224       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1225       case VariantLosers:     /* should work except for win condition,
1226                                  and doesn't know captures are mandatory */
1227       case VariantSuicide:    /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantGiveaway:   /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantTwoKings:   /* should work */
1232       case VariantAtomic:     /* should work except for win condition */
1233       case Variant3Check:     /* should work except for win condition */
1234       case VariantShatranj:   /* should work except for all win conditions */
1235       case VariantMakruk:     /* should work except for draw countdown */
1236       case VariantASEAN :     /* should work except for draw countdown */
1237       case VariantBerolina:   /* might work if TestLegality is off */
1238       case VariantCapaRandom: /* should work */
1239       case VariantJanus:      /* should work */
1240       case VariantSuper:      /* experimental */
1241       case VariantGreat:      /* experimental, requires legality testing to be off */
1242       case VariantSChess:     /* S-Chess, should work */
1243       case VariantGrand:      /* should work */
1244       case VariantSpartan:    /* should work */
1245       case VariantLion:       /* should work */
1246       case VariantChuChess:   /* should work */
1247         break;
1248       }
1249     }
1250
1251 }
1252
1253 int
1254 NextIntegerFromString (char ** str, long * value)
1255 {
1256     int result = -1;
1257     char * s = *str;
1258
1259     while( *s == ' ' || *s == '\t' ) {
1260         s++;
1261     }
1262
1263     *value = 0;
1264
1265     if( *s >= '0' && *s <= '9' ) {
1266         while( *s >= '0' && *s <= '9' ) {
1267             *value = *value * 10 + (*s - '0');
1268             s++;
1269         }
1270
1271         result = 0;
1272     }
1273
1274     *str = s;
1275
1276     return result;
1277 }
1278
1279 int
1280 NextTimeControlFromString (char ** str, long * value)
1281 {
1282     long temp;
1283     int result = NextIntegerFromString( str, &temp );
1284
1285     if( result == 0 ) {
1286         *value = temp * 60; /* Minutes */
1287         if( **str == ':' ) {
1288             (*str)++;
1289             result = NextIntegerFromString( str, &temp );
1290             *value += temp; /* Seconds */
1291         }
1292     }
1293
1294     return result;
1295 }
1296
1297 int
1298 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1299 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1300     int result = -1, type = 0; long temp, temp2;
1301
1302     if(**str != ':') return -1; // old params remain in force!
1303     (*str)++;
1304     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1305     if( NextIntegerFromString( str, &temp ) ) return -1;
1306     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1307
1308     if(**str != '/') {
1309         /* time only: incremental or sudden-death time control */
1310         if(**str == '+') { /* increment follows; read it */
1311             (*str)++;
1312             if(**str == '!') type = *(*str)++; // Bronstein TC
1313             if(result = NextIntegerFromString( str, &temp2)) return -1;
1314             *inc = temp2 * 1000;
1315             if(**str == '.') { // read fraction of increment
1316                 char *start = ++(*str);
1317                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1318                 temp2 *= 1000;
1319                 while(start++ < *str) temp2 /= 10;
1320                 *inc += temp2;
1321             }
1322         } else *inc = 0;
1323         *moves = 0; *tc = temp * 1000; *incType = type;
1324         return 0;
1325     }
1326
1327     (*str)++; /* classical time control */
1328     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1329
1330     if(result == 0) {
1331         *moves = temp;
1332         *tc    = temp2 * 1000;
1333         *inc   = 0;
1334         *incType = type;
1335     }
1336     return result;
1337 }
1338
1339 int
1340 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1341 {   /* [HGM] get time to add from the multi-session time-control string */
1342     int incType, moves=1; /* kludge to force reading of first session */
1343     long time, increment;
1344     char *s = tcString;
1345
1346     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1347     do {
1348         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1349         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1350         if(movenr == -1) return time;    /* last move before new session     */
1351         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1352         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1353         if(!moves) return increment;     /* current session is incremental   */
1354         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1355     } while(movenr >= -1);               /* try again for next session       */
1356
1357     return 0; // no new time quota on this move
1358 }
1359
1360 int
1361 ParseTimeControl (char *tc, float ti, int mps)
1362 {
1363   long tc1;
1364   long tc2;
1365   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1366   int min, sec=0;
1367
1368   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1369   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1370       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1371   if(ti > 0) {
1372
1373     if(mps)
1374       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1375     else
1376       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1377   } else {
1378     if(mps)
1379       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1380     else
1381       snprintf(buf, MSG_SIZ, ":%s", mytc);
1382   }
1383   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1384
1385   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1386     return FALSE;
1387   }
1388
1389   if( *tc == '/' ) {
1390     /* Parse second time control */
1391     tc++;
1392
1393     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1394       return FALSE;
1395     }
1396
1397     if( tc2 == 0 ) {
1398       return FALSE;
1399     }
1400
1401     timeControl_2 = tc2 * 1000;
1402   }
1403   else {
1404     timeControl_2 = 0;
1405   }
1406
1407   if( tc1 == 0 ) {
1408     return FALSE;
1409   }
1410
1411   timeControl = tc1 * 1000;
1412
1413   if (ti >= 0) {
1414     timeIncrement = ti * 1000;  /* convert to ms */
1415     movesPerSession = 0;
1416   } else {
1417     timeIncrement = 0;
1418     movesPerSession = mps;
1419   }
1420   return TRUE;
1421 }
1422
1423 void
1424 InitBackEnd2 ()
1425 {
1426     if (appData.debugMode) {
1427 #    ifdef __GIT_VERSION
1428       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1429 #    else
1430       fprintf(debugFP, "Version: %s\n", programVersion);
1431 #    endif
1432     }
1433     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1434
1435     set_cont_sequence(appData.wrapContSeq);
1436     if (appData.matchGames > 0) {
1437         appData.matchMode = TRUE;
1438     } else if (appData.matchMode) {
1439         appData.matchGames = 1;
1440     }
1441     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1442         appData.matchGames = appData.sameColorGames;
1443     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1444         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1445         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1446     }
1447     Reset(TRUE, FALSE);
1448     if (appData.noChessProgram || first.protocolVersion == 1) {
1449       InitBackEnd3();
1450     } else {
1451       /* kludge: allow timeout for initial "feature" commands */
1452       FreezeUI();
1453       DisplayMessage("", _("Starting chess program"));
1454       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1455     }
1456 }
1457
1458 int
1459 CalculateIndex (int index, int gameNr)
1460 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1461     int res;
1462     if(index > 0) return index; // fixed nmber
1463     if(index == 0) return 1;
1464     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1465     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1466     return res;
1467 }
1468
1469 int
1470 LoadGameOrPosition (int gameNr)
1471 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1472     if (*appData.loadGameFile != NULLCHAR) {
1473         if (!LoadGameFromFile(appData.loadGameFile,
1474                 CalculateIndex(appData.loadGameIndex, gameNr),
1475                               appData.loadGameFile, FALSE)) {
1476             DisplayFatalError(_("Bad game file"), 0, 1);
1477             return 0;
1478         }
1479     } else if (*appData.loadPositionFile != NULLCHAR) {
1480         if (!LoadPositionFromFile(appData.loadPositionFile,
1481                 CalculateIndex(appData.loadPositionIndex, gameNr),
1482                                   appData.loadPositionFile)) {
1483             DisplayFatalError(_("Bad position file"), 0, 1);
1484             return 0;
1485         }
1486     }
1487     return 1;
1488 }
1489
1490 void
1491 ReserveGame (int gameNr, char resChar)
1492 {
1493     FILE *tf = fopen(appData.tourneyFile, "r+");
1494     char *p, *q, c, buf[MSG_SIZ];
1495     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1496     safeStrCpy(buf, lastMsg, MSG_SIZ);
1497     DisplayMessage(_("Pick new game"), "");
1498     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1499     ParseArgsFromFile(tf);
1500     p = q = appData.results;
1501     if(appData.debugMode) {
1502       char *r = appData.participants;
1503       fprintf(debugFP, "results = '%s'\n", p);
1504       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1505       fprintf(debugFP, "\n");
1506     }
1507     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1508     nextGame = q - p;
1509     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1510     safeStrCpy(q, p, strlen(p) + 2);
1511     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1512     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1514         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1515         q[nextGame] = '*';
1516     }
1517     fseek(tf, -(strlen(p)+4), SEEK_END);
1518     c = fgetc(tf);
1519     if(c != '"') // depending on DOS or Unix line endings we can be one off
1520          fseek(tf, -(strlen(p)+2), SEEK_END);
1521     else fseek(tf, -(strlen(p)+3), SEEK_END);
1522     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1523     DisplayMessage(buf, "");
1524     free(p); appData.results = q;
1525     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1526        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1527       int round = appData.defaultMatchGames * appData.tourneyType;
1528       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1529          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1530         UnloadEngine(&first);  // next game belongs to other pairing;
1531         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1532     }
1533     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1534 }
1535
1536 void
1537 MatchEvent (int mode)
1538 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1539         int dummy;
1540         if(matchMode) { // already in match mode: switch it off
1541             abortMatch = TRUE;
1542             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1543             return;
1544         }
1545 //      if(gameMode != BeginningOfGame) {
1546 //          DisplayError(_("You can only start a match from the initial position."), 0);
1547 //          return;
1548 //      }
1549         abortMatch = FALSE;
1550         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1551         /* Set up machine vs. machine match */
1552         nextGame = 0;
1553         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1554         if(appData.tourneyFile[0]) {
1555             ReserveGame(-1, 0);
1556             if(nextGame > appData.matchGames) {
1557                 char buf[MSG_SIZ];
1558                 if(strchr(appData.results, '*') == NULL) {
1559                     FILE *f;
1560                     appData.tourneyCycles++;
1561                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1562                         fclose(f);
1563                         NextTourneyGame(-1, &dummy);
1564                         ReserveGame(-1, 0);
1565                         if(nextGame <= appData.matchGames) {
1566                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1567                             matchMode = mode;
1568                             ScheduleDelayedEvent(NextMatchGame, 10000);
1569                             return;
1570                         }
1571                     }
1572                 }
1573                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1574                 DisplayError(buf, 0);
1575                 appData.tourneyFile[0] = 0;
1576                 return;
1577             }
1578         } else
1579         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1580             DisplayFatalError(_("Can't have a match with no chess programs"),
1581                               0, 2);
1582             return;
1583         }
1584         matchMode = mode;
1585         matchGame = roundNr = 1;
1586         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1587         NextMatchGame();
1588 }
1589
1590 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1591
1592 void
1593 InitBackEnd3 P((void))
1594 {
1595     GameMode initialMode;
1596     char buf[MSG_SIZ];
1597     int err, len;
1598
1599     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1600        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1601         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1602        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1603        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1604         char c, *q = first.variants, *p = strchr(q, ',');
1605         if(p) *p = NULLCHAR;
1606         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1607             int w, h, s;
1608             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1609                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1610             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1611             Reset(TRUE, FALSE);         // and re-initialize
1612         }
1613         if(p) *p = ',';
1614     }
1615
1616     InitChessProgram(&first, startedFromSetupPosition);
1617
1618     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1619         free(programVersion);
1620         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1621         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1622         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1623     }
1624
1625     if (appData.icsActive) {
1626 #ifdef WIN32
1627         /* [DM] Make a console window if needed [HGM] merged ifs */
1628         ConsoleCreate();
1629 #endif
1630         err = establish();
1631         if (err != 0)
1632           {
1633             if (*appData.icsCommPort != NULLCHAR)
1634               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1635                              appData.icsCommPort);
1636             else
1637               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1638                         appData.icsHost, appData.icsPort);
1639
1640             if( (len >= MSG_SIZ) && appData.debugMode )
1641               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1642
1643             DisplayFatalError(buf, err, 1);
1644             return;
1645         }
1646         SetICSMode();
1647         telnetISR =
1648           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1649         fromUserISR =
1650           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1651         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1652             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1653     } else if (appData.noChessProgram) {
1654         SetNCPMode();
1655     } else {
1656         SetGNUMode();
1657     }
1658
1659     if (*appData.cmailGameName != NULLCHAR) {
1660         SetCmailMode();
1661         OpenLoopback(&cmailPR);
1662         cmailISR =
1663           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1664     }
1665
1666     ThawUI();
1667     DisplayMessage("", "");
1668     if (StrCaseCmp(appData.initialMode, "") == 0) {
1669       initialMode = BeginningOfGame;
1670       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1671         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1672         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1673         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1674         ModeHighlight();
1675       }
1676     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1677       initialMode = TwoMachinesPlay;
1678     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1679       initialMode = AnalyzeFile;
1680     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1681       initialMode = AnalyzeMode;
1682     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1683       initialMode = MachinePlaysWhite;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1685       initialMode = MachinePlaysBlack;
1686     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1687       initialMode = EditGame;
1688     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1689       initialMode = EditPosition;
1690     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1691       initialMode = Training;
1692     } else {
1693       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1694       if( (len >= MSG_SIZ) && appData.debugMode )
1695         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1696
1697       DisplayFatalError(buf, 0, 2);
1698       return;
1699     }
1700
1701     if (appData.matchMode) {
1702         if(appData.tourneyFile[0]) { // start tourney from command line
1703             FILE *f;
1704             if(f = fopen(appData.tourneyFile, "r")) {
1705                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1706                 fclose(f);
1707                 appData.clockMode = TRUE;
1708                 SetGNUMode();
1709             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1710         }
1711         MatchEvent(TRUE);
1712     } else if (*appData.cmailGameName != NULLCHAR) {
1713         /* Set up cmail mode */
1714         ReloadCmailMsgEvent(TRUE);
1715     } else {
1716         /* Set up other modes */
1717         if (initialMode == AnalyzeFile) {
1718           if (*appData.loadGameFile == NULLCHAR) {
1719             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1720             return;
1721           }
1722         }
1723         if (*appData.loadGameFile != NULLCHAR) {
1724             (void) LoadGameFromFile(appData.loadGameFile,
1725                                     appData.loadGameIndex,
1726                                     appData.loadGameFile, TRUE);
1727         } else if (*appData.loadPositionFile != NULLCHAR) {
1728             (void) LoadPositionFromFile(appData.loadPositionFile,
1729                                         appData.loadPositionIndex,
1730                                         appData.loadPositionFile);
1731             /* [HGM] try to make self-starting even after FEN load */
1732             /* to allow automatic setup of fairy variants with wtm */
1733             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1734                 gameMode = BeginningOfGame;
1735                 setboardSpoiledMachineBlack = 1;
1736             }
1737             /* [HGM] loadPos: make that every new game uses the setup */
1738             /* from file as long as we do not switch variant          */
1739             if(!blackPlaysFirst) {
1740                 startedFromPositionFile = TRUE;
1741                 CopyBoard(filePosition, boards[0]);
1742             }
1743         }
1744         if (initialMode == AnalyzeMode) {
1745           if (appData.noChessProgram) {
1746             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1747             return;
1748           }
1749           if (appData.icsActive) {
1750             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1751             return;
1752           }
1753           AnalyzeModeEvent();
1754         } else if (initialMode == AnalyzeFile) {
1755           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1756           ShowThinkingEvent();
1757           AnalyzeFileEvent();
1758           AnalysisPeriodicEvent(1);
1759         } else if (initialMode == MachinePlaysWhite) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineWhiteEvent();
1771         } else if (initialMode == MachinePlaysBlack) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           MachineBlackEvent();
1783         } else if (initialMode == TwoMachinesPlay) {
1784           if (appData.noChessProgram) {
1785             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1786                               0, 2);
1787             return;
1788           }
1789           if (appData.icsActive) {
1790             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1791                               0, 2);
1792             return;
1793           }
1794           TwoMachinesEvent();
1795         } else if (initialMode == EditGame) {
1796           EditGameEvent();
1797         } else if (initialMode == EditPosition) {
1798           EditPositionEvent();
1799         } else if (initialMode == Training) {
1800           if (*appData.loadGameFile == NULLCHAR) {
1801             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1802             return;
1803           }
1804           TrainingEvent();
1805         }
1806     }
1807 }
1808
1809 void
1810 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1811 {
1812     DisplayBook(current+1);
1813
1814     MoveHistorySet( movelist, first, last, current, pvInfoList );
1815
1816     EvalGraphSet( first, last, current, pvInfoList );
1817
1818     MakeEngineOutputTitle();
1819 }
1820
1821 /*
1822  * Establish will establish a contact to a remote host.port.
1823  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1824  *  used to talk to the host.
1825  * Returns 0 if okay, error code if not.
1826  */
1827 int
1828 establish ()
1829 {
1830     char buf[MSG_SIZ];
1831
1832     if (*appData.icsCommPort != NULLCHAR) {
1833         /* Talk to the host through a serial comm port */
1834         return OpenCommPort(appData.icsCommPort, &icsPR);
1835
1836     } else if (*appData.gateway != NULLCHAR) {
1837         if (*appData.remoteShell == NULLCHAR) {
1838             /* Use the rcmd protocol to run telnet program on a gateway host */
1839             snprintf(buf, sizeof(buf), "%s %s %s",
1840                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1841             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1842
1843         } else {
1844             /* Use the rsh program to run telnet program on a gateway host */
1845             if (*appData.remoteUser == NULLCHAR) {
1846                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1847                         appData.gateway, appData.telnetProgram,
1848                         appData.icsHost, appData.icsPort);
1849             } else {
1850                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1851                         appData.remoteShell, appData.gateway,
1852                         appData.remoteUser, appData.telnetProgram,
1853                         appData.icsHost, appData.icsPort);
1854             }
1855             return StartChildProcess(buf, "", &icsPR);
1856
1857         }
1858     } else if (appData.useTelnet) {
1859         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1860
1861     } else {
1862         /* TCP socket interface differs somewhat between
1863            Unix and NT; handle details in the front end.
1864            */
1865         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1866     }
1867 }
1868
1869 void
1870 EscapeExpand (char *p, char *q)
1871 {       // [HGM] initstring: routine to shape up string arguments
1872         while(*p++ = *q++) if(p[-1] == '\\')
1873             switch(*q++) {
1874                 case 'n': p[-1] = '\n'; break;
1875                 case 'r': p[-1] = '\r'; break;
1876                 case 't': p[-1] = '\t'; break;
1877                 case '\\': p[-1] = '\\'; break;
1878                 case 0: *p = 0; return;
1879                 default: p[-1] = q[-1]; break;
1880             }
1881 }
1882
1883 void
1884 show_bytes (FILE *fp, char *buf, int count)
1885 {
1886     while (count--) {
1887         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1888             fprintf(fp, "\\%03o", *buf & 0xff);
1889         } else {
1890             putc(*buf, fp);
1891         }
1892         buf++;
1893     }
1894     fflush(fp);
1895 }
1896
1897 /* Returns an errno value */
1898 int
1899 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1900 {
1901     char buf[8192], *p, *q, *buflim;
1902     int left, newcount, outcount;
1903
1904     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1905         *appData.gateway != NULLCHAR) {
1906         if (appData.debugMode) {
1907             fprintf(debugFP, ">ICS: ");
1908             show_bytes(debugFP, message, count);
1909             fprintf(debugFP, "\n");
1910         }
1911         return OutputToProcess(pr, message, count, outError);
1912     }
1913
1914     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1915     p = message;
1916     q = buf;
1917     left = count;
1918     newcount = 0;
1919     while (left) {
1920         if (q >= buflim) {
1921             if (appData.debugMode) {
1922                 fprintf(debugFP, ">ICS: ");
1923                 show_bytes(debugFP, buf, newcount);
1924                 fprintf(debugFP, "\n");
1925             }
1926             outcount = OutputToProcess(pr, buf, newcount, outError);
1927             if (outcount < newcount) return -1; /* to be sure */
1928             q = buf;
1929             newcount = 0;
1930         }
1931         if (*p == '\n') {
1932             *q++ = '\r';
1933             newcount++;
1934         } else if (((unsigned char) *p) == TN_IAC) {
1935             *q++ = (char) TN_IAC;
1936             newcount ++;
1937         }
1938         *q++ = *p++;
1939         newcount++;
1940         left--;
1941     }
1942     if (appData.debugMode) {
1943         fprintf(debugFP, ">ICS: ");
1944         show_bytes(debugFP, buf, newcount);
1945         fprintf(debugFP, "\n");
1946     }
1947     outcount = OutputToProcess(pr, buf, newcount, outError);
1948     if (outcount < newcount) return -1; /* to be sure */
1949     return count;
1950 }
1951
1952 void
1953 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1954 {
1955     int outError, outCount;
1956     static int gotEof = 0;
1957     static FILE *ini;
1958
1959     /* Pass data read from player on to ICS */
1960     if (count > 0) {
1961         gotEof = 0;
1962         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1963         if (outCount < count) {
1964             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965         }
1966         if(have_sent_ICS_logon == 2) {
1967           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1968             fprintf(ini, "%s", message);
1969             have_sent_ICS_logon = 3;
1970           } else
1971             have_sent_ICS_logon = 1;
1972         } else if(have_sent_ICS_logon == 3) {
1973             fprintf(ini, "%s", message);
1974             fclose(ini);
1975           have_sent_ICS_logon = 1;
1976         }
1977     } else if (count < 0) {
1978         RemoveInputSource(isr);
1979         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1980     } else if (gotEof++ > 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1983     }
1984 }
1985
1986 void
1987 KeepAlive ()
1988 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1989     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1990     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1991     SendToICS("date\n");
1992     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1993 }
1994
1995 /* added routine for printf style output to ics */
1996 void
1997 ics_printf (char *format, ...)
1998 {
1999     char buffer[MSG_SIZ];
2000     va_list args;
2001
2002     va_start(args, format);
2003     vsnprintf(buffer, sizeof(buffer), format, args);
2004     buffer[sizeof(buffer)-1] = '\0';
2005     SendToICS(buffer);
2006     va_end(args);
2007 }
2008
2009 void
2010 SendToICS (char *s)
2011 {
2012     int count, outCount, outError;
2013
2014     if (icsPR == NoProc) return;
2015
2016     count = strlen(s);
2017     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2018     if (outCount < count) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 /* This is used for sending logon scripts to the ICS. Sending
2024    without a delay causes problems when using timestamp on ICC
2025    (at least on my machine). */
2026 void
2027 SendToICSDelayed (char *s, long msdelay)
2028 {
2029     int count, outCount, outError;
2030
2031     if (icsPR == NoProc) return;
2032
2033     count = strlen(s);
2034     if (appData.debugMode) {
2035         fprintf(debugFP, ">ICS: ");
2036         show_bytes(debugFP, s, count);
2037         fprintf(debugFP, "\n");
2038     }
2039     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2040                                       msdelay);
2041     if (outCount < count) {
2042         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2043     }
2044 }
2045
2046
2047 /* Remove all highlighting escape sequences in s
2048    Also deletes any suffix starting with '('
2049    */
2050 char *
2051 StripHighlightAndTitle (char *s)
2052 {
2053     static char retbuf[MSG_SIZ];
2054     char *p = retbuf;
2055
2056     while (*s != NULLCHAR) {
2057         while (*s == '\033') {
2058             while (*s != NULLCHAR && !isalpha(*s)) s++;
2059             if (*s != NULLCHAR) s++;
2060         }
2061         while (*s != NULLCHAR && *s != '\033') {
2062             if (*s == '(' || *s == '[') {
2063                 *p = NULLCHAR;
2064                 return retbuf;
2065             }
2066             *p++ = *s++;
2067         }
2068     }
2069     *p = NULLCHAR;
2070     return retbuf;
2071 }
2072
2073 /* Remove all highlighting escape sequences in s */
2074 char *
2075 StripHighlight (char *s)
2076 {
2077     static char retbuf[MSG_SIZ];
2078     char *p = retbuf;
2079
2080     while (*s != NULLCHAR) {
2081         while (*s == '\033') {
2082             while (*s != NULLCHAR && !isalpha(*s)) s++;
2083             if (*s != NULLCHAR) s++;
2084         }
2085         while (*s != NULLCHAR && *s != '\033') {
2086             *p++ = *s++;
2087         }
2088     }
2089     *p = NULLCHAR;
2090     return retbuf;
2091 }
2092
2093 char engineVariant[MSG_SIZ];
2094 char *variantNames[] = VARIANT_NAMES;
2095 char *
2096 VariantName (VariantClass v)
2097 {
2098     if(v == VariantUnknown || *engineVariant) return engineVariant;
2099     return variantNames[v];
2100 }
2101
2102
2103 /* Identify a variant from the strings the chess servers use or the
2104    PGN Variant tag names we use. */
2105 VariantClass
2106 StringToVariant (char *e)
2107 {
2108     char *p;
2109     int wnum = -1;
2110     VariantClass v = VariantNormal;
2111     int i, found = FALSE;
2112     char buf[MSG_SIZ], c;
2113     int len;
2114
2115     if (!e) return v;
2116
2117     /* [HGM] skip over optional board-size prefixes */
2118     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2119         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2120         while( *e++ != '_');
2121     }
2122
2123     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2124         v = VariantNormal;
2125         found = TRUE;
2126     } else
2127     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2128       if (p = StrCaseStr(e, variantNames[i])) {
2129         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2130         v = (VariantClass) i;
2131         found = TRUE;
2132         break;
2133       }
2134     }
2135
2136     if (!found) {
2137       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2138           || StrCaseStr(e, "wild/fr")
2139           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2140         v = VariantFischeRandom;
2141       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2142                  (i = 1, p = StrCaseStr(e, "w"))) {
2143         p += i;
2144         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2145         if (isdigit(*p)) {
2146           wnum = atoi(p);
2147         } else {
2148           wnum = -1;
2149         }
2150         switch (wnum) {
2151         case 0: /* FICS only, actually */
2152         case 1:
2153           /* Castling legal even if K starts on d-file */
2154           v = VariantWildCastle;
2155           break;
2156         case 2:
2157         case 3:
2158         case 4:
2159           /* Castling illegal even if K & R happen to start in
2160              normal positions. */
2161           v = VariantNoCastle;
2162           break;
2163         case 5:
2164         case 7:
2165         case 8:
2166         case 10:
2167         case 11:
2168         case 12:
2169         case 13:
2170         case 14:
2171         case 15:
2172         case 18:
2173         case 19:
2174           /* Castling legal iff K & R start in normal positions */
2175           v = VariantNormal;
2176           break;
2177         case 6:
2178         case 20:
2179         case 21:
2180           /* Special wilds for position setup; unclear what to do here */
2181           v = VariantLoadable;
2182           break;
2183         case 9:
2184           /* Bizarre ICC game */
2185           v = VariantTwoKings;
2186           break;
2187         case 16:
2188           v = VariantKriegspiel;
2189           break;
2190         case 17:
2191           v = VariantLosers;
2192           break;
2193         case 22:
2194           v = VariantFischeRandom;
2195           break;
2196         case 23:
2197           v = VariantCrazyhouse;
2198           break;
2199         case 24:
2200           v = VariantBughouse;
2201           break;
2202         case 25:
2203           v = Variant3Check;
2204           break;
2205         case 26:
2206           /* Not quite the same as FICS suicide! */
2207           v = VariantGiveaway;
2208           break;
2209         case 27:
2210           v = VariantAtomic;
2211           break;
2212         case 28:
2213           v = VariantShatranj;
2214           break;
2215
2216         /* Temporary names for future ICC types.  The name *will* change in
2217            the next xboard/WinBoard release after ICC defines it. */
2218         case 29:
2219           v = Variant29;
2220           break;
2221         case 30:
2222           v = Variant30;
2223           break;
2224         case 31:
2225           v = Variant31;
2226           break;
2227         case 32:
2228           v = Variant32;
2229           break;
2230         case 33:
2231           v = Variant33;
2232           break;
2233         case 34:
2234           v = Variant34;
2235           break;
2236         case 35:
2237           v = Variant35;
2238           break;
2239         case 36:
2240           v = Variant36;
2241           break;
2242         case 37:
2243           v = VariantShogi;
2244           break;
2245         case 38:
2246           v = VariantXiangqi;
2247           break;
2248         case 39:
2249           v = VariantCourier;
2250           break;
2251         case 40:
2252           v = VariantGothic;
2253           break;
2254         case 41:
2255           v = VariantCapablanca;
2256           break;
2257         case 42:
2258           v = VariantKnightmate;
2259           break;
2260         case 43:
2261           v = VariantFairy;
2262           break;
2263         case 44:
2264           v = VariantCylinder;
2265           break;
2266         case 45:
2267           v = VariantFalcon;
2268           break;
2269         case 46:
2270           v = VariantCapaRandom;
2271           break;
2272         case 47:
2273           v = VariantBerolina;
2274           break;
2275         case 48:
2276           v = VariantJanus;
2277           break;
2278         case 49:
2279           v = VariantSuper;
2280           break;
2281         case 50:
2282           v = VariantGreat;
2283           break;
2284         case -1:
2285           /* Found "wild" or "w" in the string but no number;
2286              must assume it's normal chess. */
2287           v = VariantNormal;
2288           break;
2289         default:
2290           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2291           if( (len >= MSG_SIZ) && appData.debugMode )
2292             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2293
2294           DisplayError(buf, 0);
2295           v = VariantUnknown;
2296           break;
2297         }
2298       }
2299     }
2300     if (appData.debugMode) {
2301       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2302               e, wnum, VariantName(v));
2303     }
2304     return v;
2305 }
2306
2307 static int leftover_start = 0, leftover_len = 0;
2308 char star_match[STAR_MATCH_N][MSG_SIZ];
2309
2310 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2311    advance *index beyond it, and set leftover_start to the new value of
2312    *index; else return FALSE.  If pattern contains the character '*', it
2313    matches any sequence of characters not containing '\r', '\n', or the
2314    character following the '*' (if any), and the matched sequence(s) are
2315    copied into star_match.
2316    */
2317 int
2318 looking_at ( char *buf, int *index, char *pattern)
2319 {
2320     char *bufp = &buf[*index], *patternp = pattern;
2321     int star_count = 0;
2322     char *matchp = star_match[0];
2323
2324     for (;;) {
2325         if (*patternp == NULLCHAR) {
2326             *index = leftover_start = bufp - buf;
2327             *matchp = NULLCHAR;
2328             return TRUE;
2329         }
2330         if (*bufp == NULLCHAR) return FALSE;
2331         if (*patternp == '*') {
2332             if (*bufp == *(patternp + 1)) {
2333                 *matchp = NULLCHAR;
2334                 matchp = star_match[++star_count];
2335                 patternp += 2;
2336                 bufp++;
2337                 continue;
2338             } else if (*bufp == '\n' || *bufp == '\r') {
2339                 patternp++;
2340                 if (*patternp == NULLCHAR)
2341                   continue;
2342                 else
2343                   return FALSE;
2344             } else {
2345                 *matchp++ = *bufp++;
2346                 continue;
2347             }
2348         }
2349         if (*patternp != *bufp) return FALSE;
2350         patternp++;
2351         bufp++;
2352     }
2353 }
2354
2355 void
2356 SendToPlayer (char *data, int length)
2357 {
2358     int error, outCount;
2359     outCount = OutputToProcess(NoProc, data, length, &error);
2360     if (outCount < length) {
2361         DisplayFatalError(_("Error writing to display"), error, 1);
2362     }
2363 }
2364
2365 void
2366 PackHolding (char packed[], char *holding)
2367 {
2368     char *p = holding;
2369     char *q = packed;
2370     int runlength = 0;
2371     int curr = 9999;
2372     do {
2373         if (*p == curr) {
2374             runlength++;
2375         } else {
2376             switch (runlength) {
2377               case 0:
2378                 break;
2379               case 1:
2380                 *q++ = curr;
2381                 break;
2382               case 2:
2383                 *q++ = curr;
2384                 *q++ = curr;
2385                 break;
2386               default:
2387                 sprintf(q, "%d", runlength);
2388                 while (*q) q++;
2389                 *q++ = curr;
2390                 break;
2391             }
2392             runlength = 1;
2393             curr = *p;
2394         }
2395     } while (*p++);
2396     *q = NULLCHAR;
2397 }
2398
2399 /* Telnet protocol requests from the front end */
2400 void
2401 TelnetRequest (unsigned char ddww, unsigned char option)
2402 {
2403     unsigned char msg[3];
2404     int outCount, outError;
2405
2406     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2407
2408     if (appData.debugMode) {
2409         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2410         switch (ddww) {
2411           case TN_DO:
2412             ddwwStr = "DO";
2413             break;
2414           case TN_DONT:
2415             ddwwStr = "DONT";
2416             break;
2417           case TN_WILL:
2418             ddwwStr = "WILL";
2419             break;
2420           case TN_WONT:
2421             ddwwStr = "WONT";
2422             break;
2423           default:
2424             ddwwStr = buf1;
2425             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2426             break;
2427         }
2428         switch (option) {
2429           case TN_ECHO:
2430             optionStr = "ECHO";
2431             break;
2432           default:
2433             optionStr = buf2;
2434             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2435             break;
2436         }
2437         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2438     }
2439     msg[0] = TN_IAC;
2440     msg[1] = ddww;
2441     msg[2] = option;
2442     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2443     if (outCount < 3) {
2444         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2445     }
2446 }
2447
2448 void
2449 DoEcho ()
2450 {
2451     if (!appData.icsActive) return;
2452     TelnetRequest(TN_DO, TN_ECHO);
2453 }
2454
2455 void
2456 DontEcho ()
2457 {
2458     if (!appData.icsActive) return;
2459     TelnetRequest(TN_DONT, TN_ECHO);
2460 }
2461
2462 void
2463 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2464 {
2465     /* put the holdings sent to us by the server on the board holdings area */
2466     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2467     char p;
2468     ChessSquare piece;
2469
2470     if(gameInfo.holdingsWidth < 2)  return;
2471     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2472         return; // prevent overwriting by pre-board holdings
2473
2474     if( (int)lowestPiece >= BlackPawn ) {
2475         holdingsColumn = 0;
2476         countsColumn = 1;
2477         holdingsStartRow = BOARD_HEIGHT-1;
2478         direction = -1;
2479     } else {
2480         holdingsColumn = BOARD_WIDTH-1;
2481         countsColumn = BOARD_WIDTH-2;
2482         holdingsStartRow = 0;
2483         direction = 1;
2484     }
2485
2486     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2487         board[i][holdingsColumn] = EmptySquare;
2488         board[i][countsColumn]   = (ChessSquare) 0;
2489     }
2490     while( (p=*holdings++) != NULLCHAR ) {
2491         piece = CharToPiece( ToUpper(p) );
2492         if(piece == EmptySquare) continue;
2493         /*j = (int) piece - (int) WhitePawn;*/
2494         j = PieceToNumber(piece);
2495         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2496         if(j < 0) continue;               /* should not happen */
2497         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2498         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2499         board[holdingsStartRow+j*direction][countsColumn]++;
2500     }
2501 }
2502
2503
2504 void
2505 VariantSwitch (Board board, VariantClass newVariant)
2506 {
2507    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2508    static Board oldBoard;
2509
2510    startedFromPositionFile = FALSE;
2511    if(gameInfo.variant == newVariant) return;
2512
2513    /* [HGM] This routine is called each time an assignment is made to
2514     * gameInfo.variant during a game, to make sure the board sizes
2515     * are set to match the new variant. If that means adding or deleting
2516     * holdings, we shift the playing board accordingly
2517     * This kludge is needed because in ICS observe mode, we get boards
2518     * of an ongoing game without knowing the variant, and learn about the
2519     * latter only later. This can be because of the move list we requested,
2520     * in which case the game history is refilled from the beginning anyway,
2521     * but also when receiving holdings of a crazyhouse game. In the latter
2522     * case we want to add those holdings to the already received position.
2523     */
2524
2525
2526    if (appData.debugMode) {
2527      fprintf(debugFP, "Switch board from %s to %s\n",
2528              VariantName(gameInfo.variant), VariantName(newVariant));
2529      setbuf(debugFP, NULL);
2530    }
2531    shuffleOpenings = 0;       /* [HGM] shuffle */
2532    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2533    switch(newVariant)
2534      {
2535      case VariantShogi:
2536        newWidth = 9;  newHeight = 9;
2537        gameInfo.holdingsSize = 7;
2538      case VariantBughouse:
2539      case VariantCrazyhouse:
2540        newHoldingsWidth = 2; break;
2541      case VariantGreat:
2542        newWidth = 10;
2543      case VariantSuper:
2544        newHoldingsWidth = 2;
2545        gameInfo.holdingsSize = 8;
2546        break;
2547      case VariantGothic:
2548      case VariantCapablanca:
2549      case VariantCapaRandom:
2550        newWidth = 10;
2551      default:
2552        newHoldingsWidth = gameInfo.holdingsSize = 0;
2553      };
2554
2555    if(newWidth  != gameInfo.boardWidth  ||
2556       newHeight != gameInfo.boardHeight ||
2557       newHoldingsWidth != gameInfo.holdingsWidth ) {
2558
2559      /* shift position to new playing area, if needed */
2560      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2561        for(i=0; i<BOARD_HEIGHT; i++)
2562          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2563            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2564              board[i][j];
2565        for(i=0; i<newHeight; i++) {
2566          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2567          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2568        }
2569      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574      }
2575      board[HOLDINGS_SET] = 0;
2576      gameInfo.boardWidth  = newWidth;
2577      gameInfo.boardHeight = newHeight;
2578      gameInfo.holdingsWidth = newHoldingsWidth;
2579      gameInfo.variant = newVariant;
2580      InitDrawingSizes(-2, 0);
2581    } else gameInfo.variant = newVariant;
2582    CopyBoard(oldBoard, board);   // remember correctly formatted board
2583      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2584    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2585 }
2586
2587 static int loggedOn = FALSE;
2588
2589 /*-- Game start info cache: --*/
2590 int gs_gamenum;
2591 char gs_kind[MSG_SIZ];
2592 static char player1Name[128] = "";
2593 static char player2Name[128] = "";
2594 static char cont_seq[] = "\n\\   ";
2595 static int player1Rating = -1;
2596 static int player2Rating = -1;
2597 /*----------------------------*/
2598
2599 ColorClass curColor = ColorNormal;
2600 int suppressKibitz = 0;
2601
2602 // [HGM] seekgraph
2603 Boolean soughtPending = FALSE;
2604 Boolean seekGraphUp;
2605 #define MAX_SEEK_ADS 200
2606 #define SQUARE 0x80
2607 char *seekAdList[MAX_SEEK_ADS];
2608 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2609 float tcList[MAX_SEEK_ADS];
2610 char colorList[MAX_SEEK_ADS];
2611 int nrOfSeekAds = 0;
2612 int minRating = 1010, maxRating = 2800;
2613 int hMargin = 10, vMargin = 20, h, w;
2614 extern int squareSize, lineGap;
2615
2616 void
2617 PlotSeekAd (int i)
2618 {
2619         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2620         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2621         if(r < minRating+100 && r >=0 ) r = minRating+100;
2622         if(r > maxRating) r = maxRating;
2623         if(tc < 1.f) tc = 1.f;
2624         if(tc > 95.f) tc = 95.f;
2625         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2626         y = ((double)r - minRating)/(maxRating - minRating)
2627             * (h-vMargin-squareSize/8-1) + vMargin;
2628         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2629         if(strstr(seekAdList[i], " u ")) color = 1;
2630         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2631            !strstr(seekAdList[i], "bullet") &&
2632            !strstr(seekAdList[i], "blitz") &&
2633            !strstr(seekAdList[i], "standard") ) color = 2;
2634         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2635         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2636 }
2637
2638 void
2639 PlotSingleSeekAd (int i)
2640 {
2641         PlotSeekAd(i);
2642 }
2643
2644 void
2645 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2646 {
2647         char buf[MSG_SIZ], *ext = "";
2648         VariantClass v = StringToVariant(type);
2649         if(strstr(type, "wild")) {
2650             ext = type + 4; // append wild number
2651             if(v == VariantFischeRandom) type = "chess960"; else
2652             if(v == VariantLoadable) type = "setup"; else
2653             type = VariantName(v);
2654         }
2655         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2656         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2657             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2658             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2659             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2660             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2661             seekNrList[nrOfSeekAds] = nr;
2662             zList[nrOfSeekAds] = 0;
2663             seekAdList[nrOfSeekAds++] = StrSave(buf);
2664             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2665         }
2666 }
2667
2668 void
2669 EraseSeekDot (int i)
2670 {
2671     int x = xList[i], y = yList[i], d=squareSize/4, k;
2672     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2673     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2674     // now replot every dot that overlapped
2675     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2676         int xx = xList[k], yy = yList[k];
2677         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2678             DrawSeekDot(xx, yy, colorList[k]);
2679     }
2680 }
2681
2682 void
2683 RemoveSeekAd (int nr)
2684 {
2685         int i;
2686         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2687             EraseSeekDot(i);
2688             if(seekAdList[i]) free(seekAdList[i]);
2689             seekAdList[i] = seekAdList[--nrOfSeekAds];
2690             seekNrList[i] = seekNrList[nrOfSeekAds];
2691             ratingList[i] = ratingList[nrOfSeekAds];
2692             colorList[i]  = colorList[nrOfSeekAds];
2693             tcList[i] = tcList[nrOfSeekAds];
2694             xList[i]  = xList[nrOfSeekAds];
2695             yList[i]  = yList[nrOfSeekAds];
2696             zList[i]  = zList[nrOfSeekAds];
2697             seekAdList[nrOfSeekAds] = NULL;
2698             break;
2699         }
2700 }
2701
2702 Boolean
2703 MatchSoughtLine (char *line)
2704 {
2705     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2706     int nr, base, inc, u=0; char dummy;
2707
2708     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2709        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2710        (u=1) &&
2711        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2713         // match: compact and save the line
2714         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2715         return TRUE;
2716     }
2717     return FALSE;
2718 }
2719
2720 int
2721 DrawSeekGraph ()
2722 {
2723     int i;
2724     if(!seekGraphUp) return FALSE;
2725     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2726     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2727
2728     DrawSeekBackground(0, 0, w, h);
2729     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2730     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2731     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2732         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2733         yy = h-1-yy;
2734         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2735         if(i%500 == 0) {
2736             char buf[MSG_SIZ];
2737             snprintf(buf, MSG_SIZ, "%d", i);
2738             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2739         }
2740     }
2741     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2742     for(i=1; i<100; i+=(i<10?1:5)) {
2743         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2744         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2745         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2746             char buf[MSG_SIZ];
2747             snprintf(buf, MSG_SIZ, "%d", i);
2748             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2749         }
2750     }
2751     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2752     return TRUE;
2753 }
2754
2755 int
2756 SeekGraphClick (ClickType click, int x, int y, int moving)
2757 {
2758     static int lastDown = 0, displayed = 0, lastSecond;
2759     if(y < 0) return FALSE;
2760     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2761         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2762         if(!seekGraphUp) return FALSE;
2763         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2764         DrawPosition(TRUE, NULL);
2765         return TRUE;
2766     }
2767     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2768         if(click == Release || moving) return FALSE;
2769         nrOfSeekAds = 0;
2770         soughtPending = TRUE;
2771         SendToICS(ics_prefix);
2772         SendToICS("sought\n"); // should this be "sought all"?
2773     } else { // issue challenge based on clicked ad
2774         int dist = 10000; int i, closest = 0, second = 0;
2775         for(i=0; i<nrOfSeekAds; i++) {
2776             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2777             if(d < dist) { dist = d; closest = i; }
2778             second += (d - zList[i] < 120); // count in-range ads
2779             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2780         }
2781         if(dist < 120) {
2782             char buf[MSG_SIZ];
2783             second = (second > 1);
2784             if(displayed != closest || second != lastSecond) {
2785                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2786                 lastSecond = second; displayed = closest;
2787             }
2788             if(click == Press) {
2789                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2790                 lastDown = closest;
2791                 return TRUE;
2792             } // on press 'hit', only show info
2793             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2794             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2795             SendToICS(ics_prefix);
2796             SendToICS(buf);
2797             return TRUE; // let incoming board of started game pop down the graph
2798         } else if(click == Release) { // release 'miss' is ignored
2799             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2800             if(moving == 2) { // right up-click
2801                 nrOfSeekAds = 0; // refresh graph
2802                 soughtPending = TRUE;
2803                 SendToICS(ics_prefix);
2804                 SendToICS("sought\n"); // should this be "sought all"?
2805             }
2806             return TRUE;
2807         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2808         // press miss or release hit 'pop down' seek graph
2809         seekGraphUp = FALSE;
2810         DrawPosition(TRUE, NULL);
2811     }
2812     return TRUE;
2813 }
2814
2815 void
2816 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2817 {
2818 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2819 #define STARTED_NONE 0
2820 #define STARTED_MOVES 1
2821 #define STARTED_BOARD 2
2822 #define STARTED_OBSERVE 3
2823 #define STARTED_HOLDINGS 4
2824 #define STARTED_CHATTER 5
2825 #define STARTED_COMMENT 6
2826 #define STARTED_MOVES_NOHIDE 7
2827
2828     static int started = STARTED_NONE;
2829     static char parse[20000];
2830     static int parse_pos = 0;
2831     static char buf[BUF_SIZE + 1];
2832     static int firstTime = TRUE, intfSet = FALSE;
2833     static ColorClass prevColor = ColorNormal;
2834     static int savingComment = FALSE;
2835     static int cmatch = 0; // continuation sequence match
2836     char *bp;
2837     char str[MSG_SIZ];
2838     int i, oldi;
2839     int buf_len;
2840     int next_out;
2841     int tkind;
2842     int backup;    /* [DM] For zippy color lines */
2843     char *p;
2844     char talker[MSG_SIZ]; // [HGM] chat
2845     int channel, collective=0;
2846
2847     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2848
2849     if (appData.debugMode) {
2850       if (!error) {
2851         fprintf(debugFP, "<ICS: ");
2852         show_bytes(debugFP, data, count);
2853         fprintf(debugFP, "\n");
2854       }
2855     }
2856
2857     if (appData.debugMode) { int f = forwardMostMove;
2858         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2859                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2860                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2861     }
2862     if (count > 0) {
2863         /* If last read ended with a partial line that we couldn't parse,
2864            prepend it to the new read and try again. */
2865         if (leftover_len > 0) {
2866             for (i=0; i<leftover_len; i++)
2867               buf[i] = buf[leftover_start + i];
2868         }
2869
2870     /* copy new characters into the buffer */
2871     bp = buf + leftover_len;
2872     buf_len=leftover_len;
2873     for (i=0; i<count; i++)
2874     {
2875         // ignore these
2876         if (data[i] == '\r')
2877             continue;
2878
2879         // join lines split by ICS?
2880         if (!appData.noJoin)
2881         {
2882             /*
2883                 Joining just consists of finding matches against the
2884                 continuation sequence, and discarding that sequence
2885                 if found instead of copying it.  So, until a match
2886                 fails, there's nothing to do since it might be the
2887                 complete sequence, and thus, something we don't want
2888                 copied.
2889             */
2890             if (data[i] == cont_seq[cmatch])
2891             {
2892                 cmatch++;
2893                 if (cmatch == strlen(cont_seq))
2894                 {
2895                     cmatch = 0; // complete match.  just reset the counter
2896
2897                     /*
2898                         it's possible for the ICS to not include the space
2899                         at the end of the last word, making our [correct]
2900                         join operation fuse two separate words.  the server
2901                         does this when the space occurs at the width setting.
2902                     */
2903                     if (!buf_len || buf[buf_len-1] != ' ')
2904                     {
2905                         *bp++ = ' ';
2906                         buf_len++;
2907                     }
2908                 }
2909                 continue;
2910             }
2911             else if (cmatch)
2912             {
2913                 /*
2914                     match failed, so we have to copy what matched before
2915                     falling through and copying this character.  In reality,
2916                     this will only ever be just the newline character, but
2917                     it doesn't hurt to be precise.
2918                 */
2919                 strncpy(bp, cont_seq, cmatch);
2920                 bp += cmatch;
2921                 buf_len += cmatch;
2922                 cmatch = 0;
2923             }
2924         }
2925
2926         // copy this char
2927         *bp++ = data[i];
2928         buf_len++;
2929     }
2930
2931         buf[buf_len] = NULLCHAR;
2932 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2933         next_out = 0;
2934         leftover_start = 0;
2935
2936         i = 0;
2937         while (i < buf_len) {
2938             /* Deal with part of the TELNET option negotiation
2939                protocol.  We refuse to do anything beyond the
2940                defaults, except that we allow the WILL ECHO option,
2941                which ICS uses to turn off password echoing when we are
2942                directly connected to it.  We reject this option
2943                if localLineEditing mode is on (always on in xboard)
2944                and we are talking to port 23, which might be a real
2945                telnet server that will try to keep WILL ECHO on permanently.
2946              */
2947             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2948                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2949                 unsigned char option;
2950                 oldi = i;
2951                 switch ((unsigned char) buf[++i]) {
2952                   case TN_WILL:
2953                     if (appData.debugMode)
2954                       fprintf(debugFP, "\n<WILL ");
2955                     switch (option = (unsigned char) buf[++i]) {
2956                       case TN_ECHO:
2957                         if (appData.debugMode)
2958                           fprintf(debugFP, "ECHO ");
2959                         /* Reply only if this is a change, according
2960                            to the protocol rules. */
2961                         if (remoteEchoOption) break;
2962                         if (appData.localLineEditing &&
2963                             atoi(appData.icsPort) == TN_PORT) {
2964                             TelnetRequest(TN_DONT, TN_ECHO);
2965                         } else {
2966                             EchoOff();
2967                             TelnetRequest(TN_DO, TN_ECHO);
2968                             remoteEchoOption = TRUE;
2969                         }
2970                         break;
2971                       default:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "%d ", option);
2974                         /* Whatever this is, we don't want it. */
2975                         TelnetRequest(TN_DONT, option);
2976                         break;
2977                     }
2978                     break;
2979                   case TN_WONT:
2980                     if (appData.debugMode)
2981                       fprintf(debugFP, "\n<WONT ");
2982                     switch (option = (unsigned char) buf[++i]) {
2983                       case TN_ECHO:
2984                         if (appData.debugMode)
2985                           fprintf(debugFP, "ECHO ");
2986                         /* Reply only if this is a change, according
2987                            to the protocol rules. */
2988                         if (!remoteEchoOption) break;
2989                         EchoOn();
2990                         TelnetRequest(TN_DONT, TN_ECHO);
2991                         remoteEchoOption = FALSE;
2992                         break;
2993                       default:
2994                         if (appData.debugMode)
2995                           fprintf(debugFP, "%d ", (unsigned char) option);
2996                         /* Whatever this is, it must already be turned
2997                            off, because we never agree to turn on
2998                            anything non-default, so according to the
2999                            protocol rules, we don't reply. */
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DO:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DO ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         /* Whatever this is, we refuse to do it. */
3009                         if (appData.debugMode)
3010                           fprintf(debugFP, "%d ", option);
3011                         TelnetRequest(TN_WONT, option);
3012                         break;
3013                     }
3014                     break;
3015                   case TN_DONT:
3016                     if (appData.debugMode)
3017                       fprintf(debugFP, "\n<DONT ");
3018                     switch (option = (unsigned char) buf[++i]) {
3019                       default:
3020                         if (appData.debugMode)
3021                           fprintf(debugFP, "%d ", option);
3022                         /* Whatever this is, we are already not doing
3023                            it, because we never agree to do anything
3024                            non-default, so according to the protocol
3025                            rules, we don't reply. */
3026                         break;
3027                     }
3028                     break;
3029                   case TN_IAC:
3030                     if (appData.debugMode)
3031                       fprintf(debugFP, "\n<IAC ");
3032                     /* Doubled IAC; pass it through */
3033                     i--;
3034                     break;
3035                   default:
3036                     if (appData.debugMode)
3037                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3038                     /* Drop all other telnet commands on the floor */
3039                     break;
3040                 }
3041                 if (oldi > next_out)
3042                   SendToPlayer(&buf[next_out], oldi - next_out);
3043                 if (++i > next_out)
3044                   next_out = i;
3045                 continue;
3046             }
3047
3048             /* OK, this at least will *usually* work */
3049             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3050                 loggedOn = TRUE;
3051             }
3052
3053             if (loggedOn && !intfSet) {
3054                 if (ics_type == ICS_ICC) {
3055                   snprintf(str, MSG_SIZ,
3056                           "/set-quietly interface %s\n/set-quietly style 12\n",
3057                           programVersion);
3058                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3059                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3060                 } else if (ics_type == ICS_CHESSNET) {
3061                   snprintf(str, MSG_SIZ, "/style 12\n");
3062                 } else {
3063                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3064                   strcat(str, programVersion);
3065                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3066                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3067                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3068 #ifdef WIN32
3069                   strcat(str, "$iset nohighlight 1\n");
3070 #endif
3071                   strcat(str, "$iset lock 1\n$style 12\n");
3072                 }
3073                 SendToICS(str);
3074                 NotifyFrontendLogin();
3075                 intfSet = TRUE;
3076             }
3077
3078             if (started == STARTED_COMMENT) {
3079                 /* Accumulate characters in comment */
3080                 parse[parse_pos++] = buf[i];
3081                 if (buf[i] == '\n') {
3082                     parse[parse_pos] = NULLCHAR;
3083                     if(chattingPartner>=0) {
3084                         char mess[MSG_SIZ];
3085                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3086                         OutputChatMessage(chattingPartner, mess);
3087                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3088                             int p;
3089                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3090                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3091                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3092                                 OutputChatMessage(p, mess);
3093                                 break;
3094                             }
3095                         }
3096                         chattingPartner = -1;
3097                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3098                         collective = 0;
3099                     } else
3100                     if(!suppressKibitz) // [HGM] kibitz
3101                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3102                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3103                         int nrDigit = 0, nrAlph = 0, j;
3104                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3105                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3106                         parse[parse_pos] = NULLCHAR;
3107                         // try to be smart: if it does not look like search info, it should go to
3108                         // ICS interaction window after all, not to engine-output window.
3109                         for(j=0; j<parse_pos; j++) { // count letters and digits
3110                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3111                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3112                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3113                         }
3114                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3115                             int depth=0; float score;
3116                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3117                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3118                                 pvInfoList[forwardMostMove-1].depth = depth;
3119                                 pvInfoList[forwardMostMove-1].score = 100*score;
3120                             }
3121                             OutputKibitz(suppressKibitz, parse);
3122                         } else {
3123                             char tmp[MSG_SIZ];
3124                             if(gameMode == IcsObserving) // restore original ICS messages
3125                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3126                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3127                             else
3128                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3130                             SendToPlayer(tmp, strlen(tmp));
3131                         }
3132                         next_out = i+1; // [HGM] suppress printing in ICS window
3133                     }
3134                     started = STARTED_NONE;
3135                 } else {
3136                     /* Don't match patterns against characters in comment */
3137                     i++;
3138                     continue;
3139                 }
3140             }
3141             if (started == STARTED_CHATTER) {
3142                 if (buf[i] != '\n') {
3143                     /* Don't match patterns against characters in chatter */
3144                     i++;
3145                     continue;
3146                 }
3147                 started = STARTED_NONE;
3148                 if(suppressKibitz) next_out = i+1;
3149             }
3150
3151             /* Kludge to deal with rcmd protocol */
3152             if (firstTime && looking_at(buf, &i, "\001*")) {
3153                 DisplayFatalError(&buf[1], 0, 1);
3154                 continue;
3155             } else {
3156                 firstTime = FALSE;
3157             }
3158
3159             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3160                 ics_type = ICS_ICC;
3161                 ics_prefix = "/";
3162                 if (appData.debugMode)
3163                   fprintf(debugFP, "ics_type %d\n", ics_type);
3164                 continue;
3165             }
3166             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3167                 ics_type = ICS_FICS;
3168                 ics_prefix = "$";
3169                 if (appData.debugMode)
3170                   fprintf(debugFP, "ics_type %d\n", ics_type);
3171                 continue;
3172             }
3173             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3174                 ics_type = ICS_CHESSNET;
3175                 ics_prefix = "/";
3176                 if (appData.debugMode)
3177                   fprintf(debugFP, "ics_type %d\n", ics_type);
3178                 continue;
3179             }
3180
3181             if (!loggedOn &&
3182                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3183                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3184                  looking_at(buf, &i, "will be \"*\""))) {
3185               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3186               continue;
3187             }
3188
3189             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3190               char buf[MSG_SIZ];
3191               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3192               DisplayIcsInteractionTitle(buf);
3193               have_set_title = TRUE;
3194             }
3195
3196             /* skip finger notes */
3197             if (started == STARTED_NONE &&
3198                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3199                  (buf[i] == '1' && buf[i+1] == '0')) &&
3200                 buf[i+2] == ':' && buf[i+3] == ' ') {
3201               started = STARTED_CHATTER;
3202               i += 3;
3203               continue;
3204             }
3205
3206             oldi = i;
3207             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3208             if(appData.seekGraph) {
3209                 if(soughtPending && MatchSoughtLine(buf+i)) {
3210                     i = strstr(buf+i, "rated") - buf;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     next_out = leftover_start = i;
3213                     started = STARTED_CHATTER;
3214                     suppressKibitz = TRUE;
3215                     continue;
3216                 }
3217                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3218                         && looking_at(buf, &i, "* ads displayed")) {
3219                     soughtPending = FALSE;
3220                     seekGraphUp = TRUE;
3221                     DrawSeekGraph();
3222                     continue;
3223                 }
3224                 if(appData.autoRefresh) {
3225                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3226                         int s = (ics_type == ICS_ICC); // ICC format differs
3227                         if(seekGraphUp)
3228                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3229                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3230                         looking_at(buf, &i, "*% "); // eat prompt
3231                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3232                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = i; // suppress
3234                         continue;
3235                     }
3236                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3237                         char *p = star_match[0];
3238                         while(*p) {
3239                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3240                             while(*p && *p++ != ' '); // next
3241                         }
3242                         looking_at(buf, &i, "*% "); // eat prompt
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i;
3245                         continue;
3246                     }
3247                 }
3248             }
3249
3250             /* skip formula vars */
3251             if (started == STARTED_NONE &&
3252                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3253               started = STARTED_CHATTER;
3254               i += 3;
3255               continue;
3256             }
3257
3258             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3259             if (appData.autoKibitz && started == STARTED_NONE &&
3260                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3261                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3262                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3263                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3264                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3265                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3266                         suppressKibitz = TRUE;
3267                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = i;
3269                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3270                                 && (gameMode == IcsPlayingWhite)) ||
3271                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3272                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3273                             started = STARTED_CHATTER; // own kibitz we simply discard
3274                         else {
3275                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3276                             parse_pos = 0; parse[0] = NULLCHAR;
3277                             savingComment = TRUE;
3278                             suppressKibitz = gameMode != IcsObserving ? 2 :
3279                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3280                         }
3281                         continue;
3282                 } else
3283                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3284                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3285                          && atoi(star_match[0])) {
3286                     // suppress the acknowledgements of our own autoKibitz
3287                     char *p;
3288                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3289                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3290                     SendToPlayer(star_match[0], strlen(star_match[0]));
3291                     if(looking_at(buf, &i, "*% ")) // eat prompt
3292                         suppressKibitz = FALSE;
3293                     next_out = i;
3294                     continue;
3295                 }
3296             } // [HGM] kibitz: end of patch
3297
3298             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3299
3300             // [HGM] chat: intercept tells by users for which we have an open chat window
3301             channel = -1;
3302             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3303                                            looking_at(buf, &i, "* whispers:") ||
3304                                            looking_at(buf, &i, "* kibitzes:") ||
3305                                            looking_at(buf, &i, "* shouts:") ||
3306                                            looking_at(buf, &i, "* c-shouts:") ||
3307                                            looking_at(buf, &i, "--> * ") ||
3308                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3312                 int p;
3313                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3314                 chattingPartner = -1; collective = 0;
3315
3316                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3317                 for(p=0; p<MAX_CHAT; p++) {
3318                     collective = 1;
3319                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3320                     talker[0] = '['; strcat(talker, "] ");
3321                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3322                     chattingPartner = p; break;
3323                     }
3324                 } else
3325                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(!strcmp("kibitzes", chatPartner[p])) {
3329                         talker[0] = '['; strcat(talker, "] ");
3330                         chattingPartner = p; break;
3331                     }
3332                 } else
3333                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3334                 for(p=0; p<MAX_CHAT; p++) {
3335                     collective = 1;
3336                     if(!strcmp("whispers", chatPartner[p])) {
3337                         talker[0] = '['; strcat(talker, "] ");
3338                         chattingPartner = p; break;
3339                     }
3340                 } else
3341                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3342                   if(buf[i-8] == '-' && buf[i-3] == 't')
3343                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3344                     collective = 1;
3345                     if(!strcmp("c-shouts", chatPartner[p])) {
3346                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3347                         chattingPartner = p; break;
3348                     }
3349                   }
3350                   if(chattingPartner < 0)
3351                   for(p=0; p<MAX_CHAT; p++) {
3352                     collective = 1;
3353                     if(!strcmp("shouts", chatPartner[p])) {
3354                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3355                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3356                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3357                         chattingPartner = p; break;
3358                     }
3359                   }
3360                 }
3361                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3362                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3363                     talker[0] = 0;
3364                     Colorize(ColorTell, FALSE);
3365                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3366                     collective |= 2;
3367                     chattingPartner = p; break;
3368                 }
3369                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3370                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3371                     started = STARTED_COMMENT;
3372                     parse_pos = 0; parse[0] = NULLCHAR;
3373                     savingComment = 3 + chattingPartner; // counts as TRUE
3374                     if(collective == 3) i = oldi; else {
3375                         suppressKibitz = TRUE;
3376                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3377                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3378                         continue;
3379                     }
3380                 }
3381             } // [HGM] chat: end of patch
3382
3383           backup = i;
3384             if (appData.zippyTalk || appData.zippyPlay) {
3385                 /* [DM] Backup address for color zippy lines */
3386 #if ZIPPY
3387                if (loggedOn == TRUE)
3388                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3389                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3390 #endif
3391             } // [DM] 'else { ' deleted
3392                 if (
3393                     /* Regular tells and says */
3394                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3395                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3396                     looking_at(buf, &i, "* says: ") ||
3397                     /* Don't color "message" or "messages" output */
3398                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3399                     looking_at(buf, &i, "*. * at *:*: ") ||
3400                     looking_at(buf, &i, "--* (*:*): ") ||
3401                     /* Message notifications (same color as tells) */
3402                     looking_at(buf, &i, "* has left a message ") ||
3403                     looking_at(buf, &i, "* just sent you a message:\n") ||
3404                     /* Whispers and kibitzes */
3405                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3406                     looking_at(buf, &i, "* kibitzes: ") ||
3407                     /* Channel tells */
3408                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3409
3410                   if (tkind == 1 && strchr(star_match[0], ':')) {
3411                       /* Avoid "tells you:" spoofs in channels */
3412                      tkind = 3;
3413                   }
3414                   if (star_match[0][0] == NULLCHAR ||
3415                       strchr(star_match[0], ' ') ||
3416                       (tkind == 3 && strchr(star_match[1], ' '))) {
3417                     /* Reject bogus matches */
3418                     i = oldi;
3419                   } else {
3420                     if (appData.colorize) {
3421                       if (oldi > next_out) {
3422                         SendToPlayer(&buf[next_out], oldi - next_out);
3423                         next_out = oldi;
3424                       }
3425                       switch (tkind) {
3426                       case 1:
3427                         Colorize(ColorTell, FALSE);
3428                         curColor = ColorTell;
3429                         break;
3430                       case 2:
3431                         Colorize(ColorKibitz, FALSE);
3432                         curColor = ColorKibitz;
3433                         break;
3434                       case 3:
3435                         p = strrchr(star_match[1], '(');
3436                         if (p == NULL) {
3437                           p = star_match[1];
3438                         } else {
3439                           p++;
3440                         }
3441                         if (atoi(p) == 1) {
3442                           Colorize(ColorChannel1, FALSE);
3443                           curColor = ColorChannel1;
3444                         } else {
3445                           Colorize(ColorChannel, FALSE);
3446                           curColor = ColorChannel;
3447                         }
3448                         break;
3449                       case 5:
3450                         curColor = ColorNormal;
3451                         break;
3452                       }
3453                     }
3454                     if (started == STARTED_NONE && appData.autoComment &&
3455                         (gameMode == IcsObserving ||
3456                          gameMode == IcsPlayingWhite ||
3457                          gameMode == IcsPlayingBlack)) {
3458                       parse_pos = i - oldi;
3459                       memcpy(parse, &buf[oldi], parse_pos);
3460                       parse[parse_pos] = NULLCHAR;
3461                       started = STARTED_COMMENT;
3462                       savingComment = TRUE;
3463                     } else if(collective != 3) {
3464                       started = STARTED_CHATTER;
3465                       savingComment = FALSE;
3466                     }
3467                     loggedOn = TRUE;
3468                     continue;
3469                   }
3470                 }
3471
3472                 if (looking_at(buf, &i, "* s-shouts: ") ||
3473                     looking_at(buf, &i, "* c-shouts: ")) {
3474                     if (appData.colorize) {
3475                         if (oldi > next_out) {
3476                             SendToPlayer(&buf[next_out], oldi - next_out);
3477                             next_out = oldi;
3478                         }
3479                         Colorize(ColorSShout, FALSE);
3480                         curColor = ColorSShout;
3481                     }
3482                     loggedOn = TRUE;
3483                     started = STARTED_CHATTER;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "--->")) {
3488                     loggedOn = TRUE;
3489                     continue;
3490                 }
3491
3492                 if (looking_at(buf, &i, "* shouts: ") ||
3493                     looking_at(buf, &i, "--> ")) {
3494                     if (appData.colorize) {
3495                         if (oldi > next_out) {
3496                             SendToPlayer(&buf[next_out], oldi - next_out);
3497                             next_out = oldi;
3498                         }
3499                         Colorize(ColorShout, FALSE);
3500                         curColor = ColorShout;
3501                     }
3502                     loggedOn = TRUE;
3503                     started = STARTED_CHATTER;
3504                     continue;
3505                 }
3506
3507                 if (looking_at( buf, &i, "Challenge:")) {
3508                     if (appData.colorize) {
3509                         if (oldi > next_out) {
3510                             SendToPlayer(&buf[next_out], oldi - next_out);
3511                             next_out = oldi;
3512                         }
3513                         Colorize(ColorChallenge, FALSE);
3514                         curColor = ColorChallenge;
3515                     }
3516                     loggedOn = TRUE;
3517                     continue;
3518                 }
3519
3520                 if (looking_at(buf, &i, "* offers you") ||
3521                     looking_at(buf, &i, "* offers to be") ||
3522                     looking_at(buf, &i, "* would like to") ||
3523                     looking_at(buf, &i, "* requests to") ||
3524                     looking_at(buf, &i, "Your opponent offers") ||
3525                     looking_at(buf, &i, "Your opponent requests")) {
3526
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorRequest, FALSE);
3533                         curColor = ColorRequest;
3534                     }
3535                     continue;
3536                 }
3537
3538                 if (looking_at(buf, &i, "* (*) seeking")) {
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorSeek, FALSE);
3545                         curColor = ColorSeek;
3546                     }
3547                     continue;
3548             }
3549
3550           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3551
3552             if (looking_at(buf, &i, "\\   ")) {
3553                 if (prevColor != ColorNormal) {
3554                     if (oldi > next_out) {
3555                         SendToPlayer(&buf[next_out], oldi - next_out);
3556                         next_out = oldi;
3557                     }
3558                     Colorize(prevColor, TRUE);
3559                     curColor = prevColor;
3560                 }
3561                 if (savingComment) {
3562                     parse_pos = i - oldi;
3563                     memcpy(parse, &buf[oldi], parse_pos);
3564                     parse[parse_pos] = NULLCHAR;
3565                     started = STARTED_COMMENT;
3566                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3567                         chattingPartner = savingComment - 3; // kludge to remember the box
3568                 } else {
3569                     started = STARTED_CHATTER;
3570                 }
3571                 continue;
3572             }
3573
3574             if (looking_at(buf, &i, "Black Strength :") ||
3575                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3576                 looking_at(buf, &i, "<10>") ||
3577                 looking_at(buf, &i, "#@#")) {
3578                 /* Wrong board style */
3579                 loggedOn = TRUE;
3580                 SendToICS(ics_prefix);
3581                 SendToICS("set style 12\n");
3582                 SendToICS(ics_prefix);
3583                 SendToICS("refresh\n");
3584                 continue;
3585             }
3586
3587             if (looking_at(buf, &i, "login:")) {
3588               if (!have_sent_ICS_logon) {
3589                 if(ICSInitScript())
3590                   have_sent_ICS_logon = 1;
3591                 else // no init script was found
3592                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3593               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3595               }
3596                 continue;
3597             }
3598
3599             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3600                 (looking_at(buf, &i, "\n<12> ") ||
3601                  looking_at(buf, &i, "<12> "))) {
3602                 loggedOn = TRUE;
3603                 if (oldi > next_out) {
3604                     SendToPlayer(&buf[next_out], oldi - next_out);
3605                 }
3606                 next_out = i;
3607                 started = STARTED_BOARD;
3608                 parse_pos = 0;
3609                 continue;
3610             }
3611
3612             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3613                 looking_at(buf, &i, "<b1> ")) {
3614                 if (oldi > next_out) {
3615                     SendToPlayer(&buf[next_out], oldi - next_out);
3616                 }
3617                 next_out = i;
3618                 started = STARTED_HOLDINGS;
3619                 parse_pos = 0;
3620                 continue;
3621             }
3622
3623             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3624                 loggedOn = TRUE;
3625                 /* Header for a move list -- first line */
3626
3627                 switch (ics_getting_history) {
3628                   case H_FALSE:
3629                     switch (gameMode) {
3630                       case IcsIdle:
3631                       case BeginningOfGame:
3632                         /* User typed "moves" or "oldmoves" while we
3633                            were idle.  Pretend we asked for these
3634                            moves and soak them up so user can step
3635                            through them and/or save them.
3636                            */
3637                         Reset(FALSE, TRUE);
3638                         gameMode = IcsObserving;
3639                         ModeHighlight();
3640                         ics_gamenum = -1;
3641                         ics_getting_history = H_GOT_UNREQ_HEADER;
3642                         break;
3643                       case EditGame: /*?*/
3644                       case EditPosition: /*?*/
3645                         /* Should above feature work in these modes too? */
3646                         /* For now it doesn't */
3647                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3648                         break;
3649                       default:
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                     }
3653                     break;
3654                   case H_REQUESTED:
3655                     /* Is this the right one? */
3656                     if (gameInfo.white && gameInfo.black &&
3657                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3658                         strcmp(gameInfo.black, star_match[2]) == 0) {
3659                         /* All is well */
3660                         ics_getting_history = H_GOT_REQ_HEADER;
3661                     }
3662                     break;
3663                   case H_GOT_REQ_HEADER:
3664                   case H_GOT_UNREQ_HEADER:
3665                   case H_GOT_UNWANTED_HEADER:
3666                   case H_GETTING_MOVES:
3667                     /* Should not happen */
3668                     DisplayError(_("Error gathering move list: two headers"), 0);
3669                     ics_getting_history = H_FALSE;
3670                     break;
3671                 }
3672
3673                 /* Save player ratings into gameInfo if needed */
3674                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3675                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3676                     (gameInfo.whiteRating == -1 ||
3677                      gameInfo.blackRating == -1)) {
3678
3679                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3680                     gameInfo.blackRating = string_to_rating(star_match[3]);
3681                     if (appData.debugMode)
3682                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3683                               gameInfo.whiteRating, gameInfo.blackRating);
3684                 }
3685                 continue;
3686             }
3687
3688             if (looking_at(buf, &i,
3689               "* * match, initial time: * minute*, increment: * second")) {
3690                 /* Header for a move list -- second line */
3691                 /* Initial board will follow if this is a wild game */
3692                 if (gameInfo.event != NULL) free(gameInfo.event);
3693                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3694                 gameInfo.event = StrSave(str);
3695                 /* [HGM] we switched variant. Translate boards if needed. */
3696                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3697                 continue;
3698             }
3699
3700             if (looking_at(buf, &i, "Move  ")) {
3701                 /* Beginning of a move list */
3702                 switch (ics_getting_history) {
3703                   case H_FALSE:
3704                     /* Normally should not happen */
3705                     /* Maybe user hit reset while we were parsing */
3706                     break;
3707                   case H_REQUESTED:
3708                     /* Happens if we are ignoring a move list that is not
3709                      * the one we just requested.  Common if the user
3710                      * tries to observe two games without turning off
3711                      * getMoveList */
3712                     break;
3713                   case H_GETTING_MOVES:
3714                     /* Should not happen */
3715                     DisplayError(_("Error gathering move list: nested"), 0);
3716                     ics_getting_history = H_FALSE;
3717                     break;
3718                   case H_GOT_REQ_HEADER:
3719                     ics_getting_history = H_GETTING_MOVES;
3720                     started = STARTED_MOVES;
3721                     parse_pos = 0;
3722                     if (oldi > next_out) {
3723                         SendToPlayer(&buf[next_out], oldi - next_out);
3724                     }
3725                     break;
3726                   case H_GOT_UNREQ_HEADER:
3727                     ics_getting_history = H_GETTING_MOVES;
3728                     started = STARTED_MOVES_NOHIDE;
3729                     parse_pos = 0;
3730                     break;
3731                   case H_GOT_UNWANTED_HEADER:
3732                     ics_getting_history = H_FALSE;
3733                     break;
3734                 }
3735                 continue;
3736             }
3737
3738             if (looking_at(buf, &i, "% ") ||
3739                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3740                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3741                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3742                     soughtPending = FALSE;
3743                     seekGraphUp = TRUE;
3744                     DrawSeekGraph();
3745                 }
3746                 if(suppressKibitz) next_out = i;
3747                 savingComment = FALSE;
3748                 suppressKibitz = 0;
3749                 switch (started) {
3750                   case STARTED_MOVES:
3751                   case STARTED_MOVES_NOHIDE:
3752                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3753                     parse[parse_pos + i - oldi] = NULLCHAR;
3754                     ParseGameHistory(parse);
3755 #if ZIPPY
3756                     if (appData.zippyPlay && first.initDone) {
3757                         FeedMovesToProgram(&first, forwardMostMove);
3758                         if (gameMode == IcsPlayingWhite) {
3759                             if (WhiteOnMove(forwardMostMove)) {
3760                                 if (first.sendTime) {
3761                                   if (first.useColors) {
3762                                     SendToProgram("black\n", &first);
3763                                   }
3764                                   SendTimeRemaining(&first, TRUE);
3765                                 }
3766                                 if (first.useColors) {
3767                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3768                                 }
3769                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3770                                 first.maybeThinking = TRUE;
3771                             } else {
3772                                 if (first.usePlayother) {
3773                                   if (first.sendTime) {
3774                                     SendTimeRemaining(&first, TRUE);
3775                                   }
3776                                   SendToProgram("playother\n", &first);
3777                                   firstMove = FALSE;
3778                                 } else {
3779                                   firstMove = TRUE;
3780                                 }
3781                             }
3782                         } else if (gameMode == IcsPlayingBlack) {
3783                             if (!WhiteOnMove(forwardMostMove)) {
3784                                 if (first.sendTime) {
3785                                   if (first.useColors) {
3786                                     SendToProgram("white\n", &first);
3787                                   }
3788                                   SendTimeRemaining(&first, FALSE);
3789                                 }
3790                                 if (first.useColors) {
3791                                   SendToProgram("black\n", &first);
3792                                 }
3793                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3794                                 first.maybeThinking = TRUE;
3795                             } else {
3796                                 if (first.usePlayother) {
3797                                   if (first.sendTime) {
3798                                     SendTimeRemaining(&first, FALSE);
3799                                   }
3800                                   SendToProgram("playother\n", &first);
3801                                   firstMove = FALSE;
3802                                 } else {
3803                                   firstMove = TRUE;
3804                                 }
3805                             }
3806                         }
3807                     }
3808 #endif
3809                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3810                         /* Moves came from oldmoves or moves command
3811                            while we weren't doing anything else.
3812                            */
3813                         currentMove = forwardMostMove;
3814                         ClearHighlights();/*!!could figure this out*/
3815                         flipView = appData.flipView;
3816                         DrawPosition(TRUE, boards[currentMove]);
3817                         DisplayBothClocks();
3818                         snprintf(str, MSG_SIZ, "%s %s %s",
3819                                 gameInfo.white, _("vs."),  gameInfo.black);
3820                         DisplayTitle(str);
3821                         gameMode = IcsIdle;
3822                     } else {
3823                         /* Moves were history of an active game */
3824                         if (gameInfo.resultDetails != NULL) {
3825                             free(gameInfo.resultDetails);
3826                             gameInfo.resultDetails = NULL;
3827                         }
3828                     }
3829                     HistorySet(parseList, backwardMostMove,
3830                                forwardMostMove, currentMove-1);
3831                     DisplayMove(currentMove - 1);
3832                     if (started == STARTED_MOVES) next_out = i;
3833                     started = STARTED_NONE;
3834                     ics_getting_history = H_FALSE;
3835                     break;
3836
3837                   case STARTED_OBSERVE:
3838                     started = STARTED_NONE;
3839                     SendToICS(ics_prefix);
3840                     SendToICS("refresh\n");
3841                     break;
3842
3843                   default:
3844                     break;
3845                 }
3846                 if(bookHit) { // [HGM] book: simulate book reply
3847                     static char bookMove[MSG_SIZ]; // a bit generous?
3848
3849                     programStats.nodes = programStats.depth = programStats.time =
3850                     programStats.score = programStats.got_only_move = 0;
3851                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3852
3853                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3854                     strcat(bookMove, bookHit);
3855                     HandleMachineMove(bookMove, &first);
3856                 }
3857                 continue;
3858             }
3859
3860             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3861                  started == STARTED_HOLDINGS ||
3862                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3863                 /* Accumulate characters in move list or board */
3864                 parse[parse_pos++] = buf[i];
3865             }
3866
3867             /* Start of game messages.  Mostly we detect start of game
3868                when the first board image arrives.  On some versions
3869                of the ICS, though, we need to do a "refresh" after starting
3870                to observe in order to get the current board right away. */
3871             if (looking_at(buf, &i, "Adding game * to observation list")) {
3872                 started = STARTED_OBSERVE;
3873                 continue;
3874             }
3875
3876             /* Handle auto-observe */
3877             if (appData.autoObserve &&
3878                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3879                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3880                 char *player;
3881                 /* Choose the player that was highlighted, if any. */
3882                 if (star_match[0][0] == '\033' ||
3883                     star_match[1][0] != '\033') {
3884                     player = star_match[0];
3885                 } else {
3886                     player = star_match[2];
3887                 }
3888                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3889                         ics_prefix, StripHighlightAndTitle(player));
3890                 SendToICS(str);
3891
3892                 /* Save ratings from notify string */
3893                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3894                 player1Rating = string_to_rating(star_match[1]);
3895                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3896                 player2Rating = string_to_rating(star_match[3]);
3897
3898                 if (appData.debugMode)
3899                   fprintf(debugFP,
3900                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3901                           player1Name, player1Rating,
3902                           player2Name, player2Rating);
3903
3904                 continue;
3905             }
3906
3907             /* Deal with automatic examine mode after a game,
3908                and with IcsObserving -> IcsExamining transition */
3909             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3910                 looking_at(buf, &i, "has made you an examiner of game *")) {
3911
3912                 int gamenum = atoi(star_match[0]);
3913                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3914                     gamenum == ics_gamenum) {
3915                     /* We were already playing or observing this game;
3916                        no need to refetch history */
3917                     gameMode = IcsExamining;
3918                     if (pausing) {
3919                         pauseExamForwardMostMove = forwardMostMove;
3920                     } else if (currentMove < forwardMostMove) {
3921                         ForwardInner(forwardMostMove);
3922                     }
3923                 } else {
3924                     /* I don't think this case really can happen */
3925                     SendToICS(ics_prefix);
3926                     SendToICS("refresh\n");
3927                 }
3928                 continue;
3929             }
3930
3931             /* Error messages */
3932 //          if (ics_user_moved) {
3933             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3934                 if (looking_at(buf, &i, "Illegal move") ||
3935                     looking_at(buf, &i, "Not a legal move") ||
3936                     looking_at(buf, &i, "Your king is in check") ||
3937                     looking_at(buf, &i, "It isn't your turn") ||
3938                     looking_at(buf, &i, "It is not your move")) {
3939                     /* Illegal move */
3940                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3941                         currentMove = forwardMostMove-1;
3942                         DisplayMove(currentMove - 1); /* before DMError */
3943                         DrawPosition(FALSE, boards[currentMove]);
3944                         SwitchClocks(forwardMostMove-1); // [HGM] race
3945                         DisplayBothClocks();
3946                     }
3947                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3948                     ics_user_moved = 0;
3949                     continue;
3950                 }
3951             }
3952
3953             if (looking_at(buf, &i, "still have time") ||
3954                 looking_at(buf, &i, "not out of time") ||
3955                 looking_at(buf, &i, "either player is out of time") ||
3956                 looking_at(buf, &i, "has timeseal; checking")) {
3957                 /* We must have called his flag a little too soon */
3958                 whiteFlag = blackFlag = FALSE;
3959                 continue;
3960             }
3961
3962             if (looking_at(buf, &i, "added * seconds to") ||
3963                 looking_at(buf, &i, "seconds were added to")) {
3964                 /* Update the clocks */
3965                 SendToICS(ics_prefix);
3966                 SendToICS("refresh\n");
3967                 continue;
3968             }
3969
3970             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3971                 ics_clock_paused = TRUE;
3972                 StopClocks();
3973                 continue;
3974             }
3975
3976             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3977                 ics_clock_paused = FALSE;
3978                 StartClocks();
3979                 continue;
3980             }
3981
3982             /* Grab player ratings from the Creating: message.
3983                Note we have to check for the special case when
3984                the ICS inserts things like [white] or [black]. */
3985             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3986                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3987                 /* star_matches:
3988                    0    player 1 name (not necessarily white)
3989                    1    player 1 rating
3990                    2    empty, white, or black (IGNORED)
3991                    3    player 2 name (not necessarily black)
3992                    4    player 2 rating
3993
3994                    The names/ratings are sorted out when the game
3995                    actually starts (below).
3996                 */
3997                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3998                 player1Rating = string_to_rating(star_match[1]);
3999                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4000                 player2Rating = string_to_rating(star_match[4]);
4001
4002                 if (appData.debugMode)
4003                   fprintf(debugFP,
4004                           "Ratings from 'Creating:' %s %d, %s %d\n",
4005                           player1Name, player1Rating,
4006                           player2Name, player2Rating);
4007
4008                 continue;
4009             }
4010
4011             /* Improved generic start/end-of-game messages */
4012             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4013                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4014                 /* If tkind == 0: */
4015                 /* star_match[0] is the game number */
4016                 /*           [1] is the white player's name */
4017                 /*           [2] is the black player's name */
4018                 /* For end-of-game: */
4019                 /*           [3] is the reason for the game end */
4020                 /*           [4] is a PGN end game-token, preceded by " " */
4021                 /* For start-of-game: */
4022                 /*           [3] begins with "Creating" or "Continuing" */
4023                 /*           [4] is " *" or empty (don't care). */
4024                 int gamenum = atoi(star_match[0]);
4025                 char *whitename, *blackname, *why, *endtoken;
4026                 ChessMove endtype = EndOfFile;
4027
4028                 if (tkind == 0) {
4029                   whitename = star_match[1];
4030                   blackname = star_match[2];
4031                   why = star_match[3];
4032                   endtoken = star_match[4];
4033                 } else {
4034                   whitename = star_match[1];
4035                   blackname = star_match[3];
4036                   why = star_match[5];
4037                   endtoken = star_match[6];
4038                 }
4039
4040                 /* Game start messages */
4041                 if (strncmp(why, "Creating ", 9) == 0 ||
4042                     strncmp(why, "Continuing ", 11) == 0) {
4043                     gs_gamenum = gamenum;
4044                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4045                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4046                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4047 #if ZIPPY
4048                     if (appData.zippyPlay) {
4049                         ZippyGameStart(whitename, blackname);
4050                     }
4051 #endif /*ZIPPY*/
4052                     partnerBoardValid = FALSE; // [HGM] bughouse
4053                     continue;
4054                 }
4055
4056                 /* Game end messages */
4057                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4058                     ics_gamenum != gamenum) {
4059                     continue;
4060                 }
4061                 while (endtoken[0] == ' ') endtoken++;
4062                 switch (endtoken[0]) {
4063                   case '*':
4064                   default:
4065                     endtype = GameUnfinished;
4066                     break;
4067                   case '0':
4068                     endtype = BlackWins;
4069                     break;
4070                   case '1':
4071                     if (endtoken[1] == '/')
4072                       endtype = GameIsDrawn;
4073                     else
4074                       endtype = WhiteWins;
4075                     break;
4076                 }
4077                 GameEnds(endtype, why, GE_ICS);
4078 #if ZIPPY
4079                 if (appData.zippyPlay && first.initDone) {
4080                     ZippyGameEnd(endtype, why);
4081                     if (first.pr == NoProc) {
4082                       /* Start the next process early so that we'll
4083                          be ready for the next challenge */
4084                       StartChessProgram(&first);
4085                     }
4086                     /* Send "new" early, in case this command takes
4087                        a long time to finish, so that we'll be ready
4088                        for the next challenge. */
4089                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4090                     Reset(TRUE, TRUE);
4091                 }
4092 #endif /*ZIPPY*/
4093                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4094                 continue;
4095             }
4096
4097             if (looking_at(buf, &i, "Removing game * from observation") ||
4098                 looking_at(buf, &i, "no longer observing game *") ||
4099                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4100                 if (gameMode == IcsObserving &&
4101                     atoi(star_match[0]) == ics_gamenum)
4102                   {
4103                       /* icsEngineAnalyze */
4104                       if (appData.icsEngineAnalyze) {
4105                             ExitAnalyzeMode();
4106                             ModeHighlight();
4107                       }
4108                       StopClocks();
4109                       gameMode = IcsIdle;
4110                       ics_gamenum = -1;
4111                       ics_user_moved = FALSE;
4112                   }
4113                 continue;
4114             }
4115
4116             if (looking_at(buf, &i, "no longer examining game *")) {
4117                 if (gameMode == IcsExamining &&
4118                     atoi(star_match[0]) == ics_gamenum)
4119                   {
4120                       gameMode = IcsIdle;
4121                       ics_gamenum = -1;
4122                       ics_user_moved = FALSE;
4123                   }
4124                 continue;
4125             }
4126
4127             /* Advance leftover_start past any newlines we find,
4128                so only partial lines can get reparsed */
4129             if (looking_at(buf, &i, "\n")) {
4130                 prevColor = curColor;
4131                 if (curColor != ColorNormal) {
4132                     if (oldi > next_out) {
4133                         SendToPlayer(&buf[next_out], oldi - next_out);
4134                         next_out = oldi;
4135                     }
4136                     Colorize(ColorNormal, FALSE);
4137                     curColor = ColorNormal;
4138                 }
4139                 if (started == STARTED_BOARD) {
4140                     started = STARTED_NONE;
4141                     parse[parse_pos] = NULLCHAR;
4142                     ParseBoard12(parse);
4143                     ics_user_moved = 0;
4144
4145                     /* Send premove here */
4146                     if (appData.premove) {
4147                       char str[MSG_SIZ];
4148                       if (currentMove == 0 &&
4149                           gameMode == IcsPlayingWhite &&
4150                           appData.premoveWhite) {
4151                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4152                         if (appData.debugMode)
4153                           fprintf(debugFP, "Sending premove:\n");
4154                         SendToICS(str);
4155                       } else if (currentMove == 1 &&
4156                                  gameMode == IcsPlayingBlack &&
4157                                  appData.premoveBlack) {
4158                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4159                         if (appData.debugMode)
4160                           fprintf(debugFP, "Sending premove:\n");
4161                         SendToICS(str);
4162                       } else if (gotPremove) {
4163                         gotPremove = 0;
4164                         ClearPremoveHighlights();
4165                         if (appData.debugMode)
4166                           fprintf(debugFP, "Sending premove:\n");
4167                           UserMoveEvent(premoveFromX, premoveFromY,
4168                                         premoveToX, premoveToY,
4169                                         premovePromoChar);
4170                       }
4171                     }
4172
4173                     /* Usually suppress following prompt */
4174                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4175                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4176                         if (looking_at(buf, &i, "*% ")) {
4177                             savingComment = FALSE;
4178                             suppressKibitz = 0;
4179                         }
4180                     }
4181                     next_out = i;
4182                 } else if (started == STARTED_HOLDINGS) {
4183                     int gamenum;
4184                     char new_piece[MSG_SIZ];
4185                     started = STARTED_NONE;
4186                     parse[parse_pos] = NULLCHAR;
4187                     if (appData.debugMode)
4188                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4189                                                         parse, currentMove);
4190                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4191                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4192                         if (gameInfo.variant == VariantNormal) {
4193                           /* [HGM] We seem to switch variant during a game!
4194                            * Presumably no holdings were displayed, so we have
4195                            * to move the position two files to the right to
4196                            * create room for them!
4197                            */
4198                           VariantClass newVariant;
4199                           switch(gameInfo.boardWidth) { // base guess on board width
4200                                 case 9:  newVariant = VariantShogi; break;
4201                                 case 10: newVariant = VariantGreat; break;
4202                                 default: newVariant = VariantCrazyhouse; break;
4203                           }
4204                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4205                           /* Get a move list just to see the header, which
4206                              will tell us whether this is really bug or zh */
4207                           if (ics_getting_history == H_FALSE) {
4208                             ics_getting_history = H_REQUESTED;
4209                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4210                             SendToICS(str);
4211                           }
4212                         }
4213                         new_piece[0] = NULLCHAR;
4214                         sscanf(parse, "game %d white [%s black [%s <- %s",
4215                                &gamenum, white_holding, black_holding,
4216                                new_piece);
4217                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4218                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4219                         /* [HGM] copy holdings to board holdings area */
4220                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4221                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4222                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4223 #if ZIPPY
4224                         if (appData.zippyPlay && first.initDone) {
4225                             ZippyHoldings(white_holding, black_holding,
4226                                           new_piece);
4227                         }
4228 #endif /*ZIPPY*/
4229                         if (tinyLayout || smallLayout) {
4230                             char wh[16], bh[16];
4231                             PackHolding(wh, white_holding);
4232                             PackHolding(bh, black_holding);
4233                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4234                                     gameInfo.white, gameInfo.black);
4235                         } else {
4236                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4237                                     gameInfo.white, white_holding, _("vs."),
4238                                     gameInfo.black, black_holding);
4239                         }
4240                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4241                         DrawPosition(FALSE, boards[currentMove]);
4242                         DisplayTitle(str);
4243                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4244                         sscanf(parse, "game %d white [%s black [%s <- %s",
4245                                &gamenum, white_holding, black_holding,
4246                                new_piece);
4247                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4248                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4249                         /* [HGM] copy holdings to partner-board holdings area */
4250                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4251                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4252                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4253                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4254                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4255                       }
4256                     }
4257                     /* Suppress following prompt */
4258                     if (looking_at(buf, &i, "*% ")) {
4259                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4260                         savingComment = FALSE;
4261                         suppressKibitz = 0;
4262                     }
4263                     next_out = i;
4264                 }
4265                 continue;
4266             }
4267
4268             i++;                /* skip unparsed character and loop back */
4269         }
4270
4271         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4272 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4273 //          SendToPlayer(&buf[next_out], i - next_out);
4274             started != STARTED_HOLDINGS && leftover_start > next_out) {
4275             SendToPlayer(&buf[next_out], leftover_start - next_out);
4276             next_out = i;
4277         }
4278
4279         leftover_len = buf_len - leftover_start;
4280         /* if buffer ends with something we couldn't parse,
4281            reparse it after appending the next read */
4282
4283     } else if (count == 0) {
4284         RemoveInputSource(isr);
4285         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4286     } else {
4287         DisplayFatalError(_("Error reading from ICS"), error, 1);
4288     }
4289 }
4290
4291
4292 /* Board style 12 looks like this:
4293
4294    <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
4295
4296  * The "<12> " is stripped before it gets to this routine.  The two
4297  * trailing 0's (flip state and clock ticking) are later addition, and
4298  * some chess servers may not have them, or may have only the first.
4299  * Additional trailing fields may be added in the future.
4300  */
4301
4302 #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"
4303
4304 #define RELATION_OBSERVING_PLAYED    0
4305 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4306 #define RELATION_PLAYING_MYMOVE      1
4307 #define RELATION_PLAYING_NOTMYMOVE  -1
4308 #define RELATION_EXAMINING           2
4309 #define RELATION_ISOLATED_BOARD     -3
4310 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4311
4312 void
4313 ParseBoard12 (char *string)
4314 {
4315 #if ZIPPY
4316     int i, takeback;
4317     char *bookHit = NULL; // [HGM] book
4318 #endif
4319     GameMode newGameMode;
4320     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4321     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4322     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4323     char to_play, board_chars[200];
4324     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4325     char black[32], white[32];
4326     Board board;
4327     int prevMove = currentMove;
4328     int ticking = 2;
4329     ChessMove moveType;
4330     int fromX, fromY, toX, toY;
4331     char promoChar;
4332     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4333     Boolean weird = FALSE, reqFlag = FALSE;
4334
4335     fromX = fromY = toX = toY = -1;
4336
4337     newGame = FALSE;
4338
4339     if (appData.debugMode)
4340       fprintf(debugFP, "Parsing board: %s\n", string);
4341
4342     move_str[0] = NULLCHAR;
4343     elapsed_time[0] = NULLCHAR;
4344     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4345         int  i = 0, j;
4346         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4347             if(string[i] == ' ') { ranks++; files = 0; }
4348             else files++;
4349             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4350             i++;
4351         }
4352         for(j = 0; j <i; j++) board_chars[j] = string[j];
4353         board_chars[i] = '\0';
4354         string += i + 1;
4355     }
4356     n = sscanf(string, PATTERN, &to_play, &double_push,
4357                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4358                &gamenum, white, black, &relation, &basetime, &increment,
4359                &white_stren, &black_stren, &white_time, &black_time,
4360                &moveNum, str, elapsed_time, move_str, &ics_flip,
4361                &ticking);
4362
4363     if (n < 21) {
4364         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4365         DisplayError(str, 0);
4366         return;
4367     }
4368
4369     /* Convert the move number to internal form */
4370     moveNum = (moveNum - 1) * 2;
4371     if (to_play == 'B') moveNum++;
4372     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4373       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4374                         0, 1);
4375       return;
4376     }
4377
4378     switch (relation) {
4379       case RELATION_OBSERVING_PLAYED:
4380       case RELATION_OBSERVING_STATIC:
4381         if (gamenum == -1) {
4382             /* Old ICC buglet */
4383             relation = RELATION_OBSERVING_STATIC;
4384         }
4385         newGameMode = IcsObserving;
4386         break;
4387       case RELATION_PLAYING_MYMOVE:
4388       case RELATION_PLAYING_NOTMYMOVE:
4389         newGameMode =
4390           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4391             IcsPlayingWhite : IcsPlayingBlack;
4392         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4393         break;
4394       case RELATION_EXAMINING:
4395         newGameMode = IcsExamining;
4396         break;
4397       case RELATION_ISOLATED_BOARD:
4398       default:
4399         /* Just display this board.  If user was doing something else,
4400            we will forget about it until the next board comes. */
4401         newGameMode = IcsIdle;
4402         break;
4403       case RELATION_STARTING_POSITION:
4404         newGameMode = gameMode;
4405         break;
4406     }
4407
4408     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4409         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4410          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4411       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4412       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4413       static int lastBgGame = -1;
4414       char *toSqr;
4415       for (k = 0; k < ranks; k++) {
4416         for (j = 0; j < files; j++)
4417           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4418         if(gameInfo.holdingsWidth > 1) {
4419              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4420              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4421         }
4422       }
4423       CopyBoard(partnerBoard, board);
4424       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4425         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4426         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4427       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4428       if(toSqr = strchr(str, '-')) {
4429         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4430         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4431       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4432       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4433       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4434       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4435       if(twoBoards) {
4436           DisplayWhiteClock(white_time*fac, to_play == 'W');
4437           DisplayBlackClock(black_time*fac, to_play != 'W');
4438           activePartner = to_play;
4439           if(gamenum != lastBgGame) {
4440               char buf[MSG_SIZ];
4441               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4442               DisplayTitle(buf);
4443           }
4444           lastBgGame = gamenum;
4445           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4446                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4447       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4448                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4449       if(!twoBoards) DisplayMessage(partnerStatus, "");
4450         partnerBoardValid = TRUE;
4451       return;
4452     }
4453
4454     if(appData.dualBoard && appData.bgObserve) {
4455         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4456             SendToICS(ics_prefix), SendToICS("pobserve\n");
4457         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4458             char buf[MSG_SIZ];
4459             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4460             SendToICS(buf);
4461         }
4462     }
4463
4464     /* Modify behavior for initial board display on move listing
4465        of wild games.
4466        */
4467     switch (ics_getting_history) {
4468       case H_FALSE:
4469       case H_REQUESTED:
4470         break;
4471       case H_GOT_REQ_HEADER:
4472       case H_GOT_UNREQ_HEADER:
4473         /* This is the initial position of the current game */
4474         gamenum = ics_gamenum;
4475         moveNum = 0;            /* old ICS bug workaround */
4476         if (to_play == 'B') {
4477           startedFromSetupPosition = TRUE;
4478           blackPlaysFirst = TRUE;
4479           moveNum = 1;
4480           if (forwardMostMove == 0) forwardMostMove = 1;
4481           if (backwardMostMove == 0) backwardMostMove = 1;
4482           if (currentMove == 0) currentMove = 1;
4483         }
4484         newGameMode = gameMode;
4485         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4486         break;
4487       case H_GOT_UNWANTED_HEADER:
4488         /* This is an initial board that we don't want */
4489         return;
4490       case H_GETTING_MOVES:
4491         /* Should not happen */
4492         DisplayError(_("Error gathering move list: extra board"), 0);
4493         ics_getting_history = H_FALSE;
4494         return;
4495     }
4496
4497    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4498                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4499                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4500      /* [HGM] We seem to have switched variant unexpectedly
4501       * Try to guess new variant from board size
4502       */
4503           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4504           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4505           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4506           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4507           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4508           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4509           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4510           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4511           /* Get a move list just to see the header, which
4512              will tell us whether this is really bug or zh */
4513           if (ics_getting_history == H_FALSE) {
4514             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4515             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4516             SendToICS(str);
4517           }
4518     }
4519
4520     /* Take action if this is the first board of a new game, or of a
4521        different game than is currently being displayed.  */
4522     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4523         relation == RELATION_ISOLATED_BOARD) {
4524
4525         /* Forget the old game and get the history (if any) of the new one */
4526         if (gameMode != BeginningOfGame) {
4527           Reset(TRUE, TRUE);
4528         }
4529         newGame = TRUE;
4530         if (appData.autoRaiseBoard) BoardToTop();
4531         prevMove = -3;
4532         if (gamenum == -1) {
4533             newGameMode = IcsIdle;
4534         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4535                    appData.getMoveList && !reqFlag) {
4536             /* Need to get game history */
4537             ics_getting_history = H_REQUESTED;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540         }
4541
4542         /* Initially flip the board to have black on the bottom if playing
4543            black or if the ICS flip flag is set, but let the user change
4544            it with the Flip View button. */
4545         flipView = appData.autoFlipView ?
4546           (newGameMode == IcsPlayingBlack) || ics_flip :
4547           appData.flipView;
4548
4549         /* Done with values from previous mode; copy in new ones */
4550         gameMode = newGameMode;
4551         ModeHighlight();
4552         ics_gamenum = gamenum;
4553         if (gamenum == gs_gamenum) {
4554             int klen = strlen(gs_kind);
4555             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4556             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4557             gameInfo.event = StrSave(str);
4558         } else {
4559             gameInfo.event = StrSave("ICS game");
4560         }
4561         gameInfo.site = StrSave(appData.icsHost);
4562         gameInfo.date = PGNDate();
4563         gameInfo.round = StrSave("-");
4564         gameInfo.white = StrSave(white);
4565         gameInfo.black = StrSave(black);
4566         timeControl = basetime * 60 * 1000;
4567         timeControl_2 = 0;
4568         timeIncrement = increment * 1000;
4569         movesPerSession = 0;
4570         gameInfo.timeControl = TimeControlTagValue();
4571         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4572   if (appData.debugMode) {
4573     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4574     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4575     setbuf(debugFP, NULL);
4576   }
4577
4578         gameInfo.outOfBook = NULL;
4579
4580         /* Do we have the ratings? */
4581         if (strcmp(player1Name, white) == 0 &&
4582             strcmp(player2Name, black) == 0) {
4583             if (appData.debugMode)
4584               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4585                       player1Rating, player2Rating);
4586             gameInfo.whiteRating = player1Rating;
4587             gameInfo.blackRating = player2Rating;
4588         } else if (strcmp(player2Name, white) == 0 &&
4589                    strcmp(player1Name, black) == 0) {
4590             if (appData.debugMode)
4591               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4592                       player2Rating, player1Rating);
4593             gameInfo.whiteRating = player2Rating;
4594             gameInfo.blackRating = player1Rating;
4595         }
4596         player1Name[0] = player2Name[0] = NULLCHAR;
4597
4598         /* Silence shouts if requested */
4599         if (appData.quietPlay &&
4600             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4601             SendToICS(ics_prefix);
4602             SendToICS("set shout 0\n");
4603         }
4604     }
4605
4606     /* Deal with midgame name changes */
4607     if (!newGame) {
4608         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4609             if (gameInfo.white) free(gameInfo.white);
4610             gameInfo.white = StrSave(white);
4611         }
4612         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4613             if (gameInfo.black) free(gameInfo.black);
4614             gameInfo.black = StrSave(black);
4615         }
4616     }
4617
4618     /* Throw away game result if anything actually changes in examine mode */
4619     if (gameMode == IcsExamining && !newGame) {
4620         gameInfo.result = GameUnfinished;
4621         if (gameInfo.resultDetails != NULL) {
4622             free(gameInfo.resultDetails);
4623             gameInfo.resultDetails = NULL;
4624         }
4625     }
4626
4627     /* In pausing && IcsExamining mode, we ignore boards coming
4628        in if they are in a different variation than we are. */
4629     if (pauseExamInvalid) return;
4630     if (pausing && gameMode == IcsExamining) {
4631         if (moveNum <= pauseExamForwardMostMove) {
4632             pauseExamInvalid = TRUE;
4633             forwardMostMove = pauseExamForwardMostMove;
4634             return;
4635         }
4636     }
4637
4638   if (appData.debugMode) {
4639     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4640   }
4641     /* Parse the board */
4642     for (k = 0; k < ranks; k++) {
4643       for (j = 0; j < files; j++)
4644         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4645       if(gameInfo.holdingsWidth > 1) {
4646            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4647            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4648       }
4649     }
4650     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4651       board[5][BOARD_RGHT+1] = WhiteAngel;
4652       board[6][BOARD_RGHT+1] = WhiteMarshall;
4653       board[1][0] = BlackMarshall;
4654       board[2][0] = BlackAngel;
4655       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4656     }
4657     CopyBoard(boards[moveNum], board);
4658     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4659     if (moveNum == 0) {
4660         startedFromSetupPosition =
4661           !CompareBoards(board, initialPosition);
4662         if(startedFromSetupPosition)
4663             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4664     }
4665
4666     /* [HGM] Set castling rights. Take the outermost Rooks,
4667        to make it also work for FRC opening positions. Note that board12
4668        is really defective for later FRC positions, as it has no way to
4669        indicate which Rook can castle if they are on the same side of King.
4670        For the initial position we grant rights to the outermost Rooks,
4671        and remember thos rights, and we then copy them on positions
4672        later in an FRC game. This means WB might not recognize castlings with
4673        Rooks that have moved back to their original position as illegal,
4674        but in ICS mode that is not its job anyway.
4675     */
4676     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4677     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4678
4679         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4680             if(board[0][i] == WhiteRook) j = i;
4681         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4682         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4683             if(board[0][i] == WhiteRook) j = i;
4684         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4686             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4687         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4688         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4690         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4691
4692         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4693         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4694         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4695             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[BOARD_HEIGHT-1][k] == bKing)
4698                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4699         if(gameInfo.variant == VariantTwoKings) {
4700             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4701             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4702             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4703         }
4704     } else { int r;
4705         r = boards[moveNum][CASTLING][0] = initialRights[0];
4706         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4707         r = boards[moveNum][CASTLING][1] = initialRights[1];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4709         r = boards[moveNum][CASTLING][3] = initialRights[3];
4710         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4711         r = boards[moveNum][CASTLING][4] = initialRights[4];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4713         /* wildcastle kludge: always assume King has rights */
4714         r = boards[moveNum][CASTLING][2] = initialRights[2];
4715         r = boards[moveNum][CASTLING][5] = initialRights[5];
4716     }
4717     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4718     boards[moveNum][EP_STATUS] = EP_NONE;
4719     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4720     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4721     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4722
4723
4724     if (ics_getting_history == H_GOT_REQ_HEADER ||
4725         ics_getting_history == H_GOT_UNREQ_HEADER) {
4726         /* This was an initial position from a move list, not
4727            the current position */
4728         return;
4729     }
4730
4731     /* Update currentMove and known move number limits */
4732     newMove = newGame || moveNum > forwardMostMove;
4733
4734     if (newGame) {
4735         forwardMostMove = backwardMostMove = currentMove = moveNum;
4736         if (gameMode == IcsExamining && moveNum == 0) {
4737           /* Workaround for ICS limitation: we are not told the wild
4738              type when starting to examine a game.  But if we ask for
4739              the move list, the move list header will tell us */
4740             ics_getting_history = H_REQUESTED;
4741             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4742             SendToICS(str);
4743         }
4744     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4745                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4746 #if ZIPPY
4747         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4748         /* [HGM] applied this also to an engine that is silently watching        */
4749         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4750             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4751             gameInfo.variant == currentlyInitializedVariant) {
4752           takeback = forwardMostMove - moveNum;
4753           for (i = 0; i < takeback; i++) {
4754             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4755             SendToProgram("undo\n", &first);
4756           }
4757         }
4758 #endif
4759
4760         forwardMostMove = moveNum;
4761         if (!pausing || currentMove > forwardMostMove)
4762           currentMove = forwardMostMove;
4763     } else {
4764         /* New part of history that is not contiguous with old part */
4765         if (pausing && gameMode == IcsExamining) {
4766             pauseExamInvalid = TRUE;
4767             forwardMostMove = pauseExamForwardMostMove;
4768             return;
4769         }
4770         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4771 #if ZIPPY
4772             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4773                 // [HGM] when we will receive the move list we now request, it will be
4774                 // fed to the engine from the first move on. So if the engine is not
4775                 // in the initial position now, bring it there.
4776                 InitChessProgram(&first, 0);
4777             }
4778 #endif
4779             ics_getting_history = H_REQUESTED;
4780             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4781             SendToICS(str);
4782         }
4783         forwardMostMove = backwardMostMove = currentMove = moveNum;
4784     }
4785
4786     /* Update the clocks */
4787     if (strchr(elapsed_time, '.')) {
4788       /* Time is in ms */
4789       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4790       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4791     } else {
4792       /* Time is in seconds */
4793       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4794       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4795     }
4796
4797
4798 #if ZIPPY
4799     if (appData.zippyPlay && newGame &&
4800         gameMode != IcsObserving && gameMode != IcsIdle &&
4801         gameMode != IcsExamining)
4802       ZippyFirstBoard(moveNum, basetime, increment);
4803 #endif
4804
4805     /* Put the move on the move list, first converting
4806        to canonical algebraic form. */
4807     if (moveNum > 0) {
4808   if (appData.debugMode) {
4809     int f = forwardMostMove;
4810     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4811             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4812             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4813     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4814     fprintf(debugFP, "moveNum = %d\n", moveNum);
4815     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4816     setbuf(debugFP, NULL);
4817   }
4818         if (moveNum <= backwardMostMove) {
4819             /* We don't know what the board looked like before
4820                this move.  Punt. */
4821           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4822             strcat(parseList[moveNum - 1], " ");
4823             strcat(parseList[moveNum - 1], elapsed_time);
4824             moveList[moveNum - 1][0] = NULLCHAR;
4825         } else if (strcmp(move_str, "none") == 0) {
4826             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4827             /* Again, we don't know what the board looked like;
4828                this is really the start of the game. */
4829             parseList[moveNum - 1][0] = NULLCHAR;
4830             moveList[moveNum - 1][0] = NULLCHAR;
4831             backwardMostMove = moveNum;
4832             startedFromSetupPosition = TRUE;
4833             fromX = fromY = toX = toY = -1;
4834         } else {
4835           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4836           //                 So we parse the long-algebraic move string in stead of the SAN move
4837           int valid; char buf[MSG_SIZ], *prom;
4838
4839           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4840                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4841           // str looks something like "Q/a1-a2"; kill the slash
4842           if(str[1] == '/')
4843             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4844           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4845           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4846                 strcat(buf, prom); // long move lacks promo specification!
4847           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4848                 if(appData.debugMode)
4849                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4850                 safeStrCpy(move_str, buf, MSG_SIZ);
4851           }
4852           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4853                                 &fromX, &fromY, &toX, &toY, &promoChar)
4854                || ParseOneMove(buf, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar);
4856           // end of long SAN patch
4857           if (valid) {
4858             (void) CoordsToAlgebraic(boards[moveNum - 1],
4859                                      PosFlags(moveNum - 1),
4860                                      fromY, fromX, toY, toX, promoChar,
4861                                      parseList[moveNum-1]);
4862             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4863               case MT_NONE:
4864               case MT_STALEMATE:
4865               default:
4866                 break;
4867               case MT_CHECK:
4868                 if(!IS_SHOGI(gameInfo.variant))
4869                     strcat(parseList[moveNum - 1], "+");
4870                 break;
4871               case MT_CHECKMATE:
4872               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4873                 strcat(parseList[moveNum - 1], "#");
4874                 break;
4875             }
4876             strcat(parseList[moveNum - 1], " ");
4877             strcat(parseList[moveNum - 1], elapsed_time);
4878             /* currentMoveString is set as a side-effect of ParseOneMove */
4879             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4880             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4881             strcat(moveList[moveNum - 1], "\n");
4882
4883             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4884                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4885               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4886                 ChessSquare old, new = boards[moveNum][k][j];
4887                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4888                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4889                   if(old == new) continue;
4890                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4891                   else if(new == WhiteWazir || new == BlackWazir) {
4892                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4893                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4894                       else boards[moveNum][k][j] = old; // preserve type of Gold
4895                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4896                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4897               }
4898           } else {
4899             /* Move from ICS was illegal!?  Punt. */
4900             if (appData.debugMode) {
4901               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4902               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4903             }
4904             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4905             strcat(parseList[moveNum - 1], " ");
4906             strcat(parseList[moveNum - 1], elapsed_time);
4907             moveList[moveNum - 1][0] = NULLCHAR;
4908             fromX = fromY = toX = toY = -1;
4909           }
4910         }
4911   if (appData.debugMode) {
4912     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4913     setbuf(debugFP, NULL);
4914   }
4915
4916 #if ZIPPY
4917         /* Send move to chess program (BEFORE animating it). */
4918         if (appData.zippyPlay && !newGame && newMove &&
4919            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4920
4921             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4922                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4923                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4924                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4925                             move_str);
4926                     DisplayError(str, 0);
4927                 } else {
4928                     if (first.sendTime) {
4929                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4930                     }
4931                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4932                     if (firstMove && !bookHit) {
4933                         firstMove = FALSE;
4934                         if (first.useColors) {
4935                           SendToProgram(gameMode == IcsPlayingWhite ?
4936                                         "white\ngo\n" :
4937                                         "black\ngo\n", &first);
4938                         } else {
4939                           SendToProgram("go\n", &first);
4940                         }
4941                         first.maybeThinking = TRUE;
4942                     }
4943                 }
4944             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4945               if (moveList[moveNum - 1][0] == NULLCHAR) {
4946                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4947                 DisplayError(str, 0);
4948               } else {
4949                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4950                 SendMoveToProgram(moveNum - 1, &first);
4951               }
4952             }
4953         }
4954 #endif
4955     }
4956
4957     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4958         /* If move comes from a remote source, animate it.  If it
4959            isn't remote, it will have already been animated. */
4960         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4961             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4962         }
4963         if (!pausing && appData.highlightLastMove) {
4964             SetHighlights(fromX, fromY, toX, toY);
4965         }
4966     }
4967
4968     /* Start the clocks */
4969     whiteFlag = blackFlag = FALSE;
4970     appData.clockMode = !(basetime == 0 && increment == 0);
4971     if (ticking == 0) {
4972       ics_clock_paused = TRUE;
4973       StopClocks();
4974     } else if (ticking == 1) {
4975       ics_clock_paused = FALSE;
4976     }
4977     if (gameMode == IcsIdle ||
4978         relation == RELATION_OBSERVING_STATIC ||
4979         relation == RELATION_EXAMINING ||
4980         ics_clock_paused)
4981       DisplayBothClocks();
4982     else
4983       StartClocks();
4984
4985     /* Display opponents and material strengths */
4986     if (gameInfo.variant != VariantBughouse &&
4987         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4988         if (tinyLayout || smallLayout) {
4989             if(gameInfo.variant == VariantNormal)
4990               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4991                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4992                     basetime, increment);
4993             else
4994               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4995                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4996                     basetime, increment, (int) gameInfo.variant);
4997         } else {
4998             if(gameInfo.variant == VariantNormal)
4999               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5000                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5001                     basetime, increment);
5002             else
5003               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5004                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5005                     basetime, increment, VariantName(gameInfo.variant));
5006         }
5007         DisplayTitle(str);
5008   if (appData.debugMode) {
5009     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5010   }
5011     }
5012
5013
5014     /* Display the board */
5015     if (!pausing && !appData.noGUI) {
5016
5017       if (appData.premove)
5018           if (!gotPremove ||
5019              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5020              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5021               ClearPremoveHighlights();
5022
5023       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5024         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5025       DrawPosition(j, boards[currentMove]);
5026
5027       DisplayMove(moveNum - 1);
5028       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5029             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5030               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5031         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5032       }
5033     }
5034
5035     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5036 #if ZIPPY
5037     if(bookHit) { // [HGM] book: simulate book reply
5038         static char bookMove[MSG_SIZ]; // a bit generous?
5039
5040         programStats.nodes = programStats.depth = programStats.time =
5041         programStats.score = programStats.got_only_move = 0;
5042         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5043
5044         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5045         strcat(bookMove, bookHit);
5046         HandleMachineMove(bookMove, &first);
5047     }
5048 #endif
5049 }
5050
5051 void
5052 GetMoveListEvent ()
5053 {
5054     char buf[MSG_SIZ];
5055     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5056         ics_getting_history = H_REQUESTED;
5057         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5058         SendToICS(buf);
5059     }
5060 }
5061
5062 void
5063 SendToBoth (char *msg)
5064 {   // to make it easy to keep two engines in step in dual analysis
5065     SendToProgram(msg, &first);
5066     if(second.analyzing) SendToProgram(msg, &second);
5067 }
5068
5069 void
5070 AnalysisPeriodicEvent (int force)
5071 {
5072     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5073          && !force) || !appData.periodicUpdates)
5074       return;
5075
5076     /* Send . command to Crafty to collect stats */
5077     SendToBoth(".\n");
5078
5079     /* Don't send another until we get a response (this makes
5080        us stop sending to old Crafty's which don't understand
5081        the "." command (sending illegal cmds resets node count & time,
5082        which looks bad)) */
5083     programStats.ok_to_send = 0;
5084 }
5085
5086 void
5087 ics_update_width (int new_width)
5088 {
5089         ics_printf("set width %d\n", new_width);
5090 }
5091
5092 void
5093 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5094 {
5095     char buf[MSG_SIZ];
5096
5097     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5098         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5099             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5100             SendToProgram(buf, cps);
5101             return;
5102         }
5103         // null move in variant where engine does not understand it (for analysis purposes)
5104         SendBoard(cps, moveNum + 1); // send position after move in stead.
5105         return;
5106     }
5107     if (cps->useUsermove) {
5108       SendToProgram("usermove ", cps);
5109     }
5110     if (cps->useSAN) {
5111       char *space;
5112       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5113         int len = space - parseList[moveNum];
5114         memcpy(buf, parseList[moveNum], len);
5115         buf[len++] = '\n';
5116         buf[len] = NULLCHAR;
5117       } else {
5118         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5119       }
5120       SendToProgram(buf, cps);
5121     } else {
5122       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5123         AlphaRank(moveList[moveNum], 4);
5124         SendToProgram(moveList[moveNum], cps);
5125         AlphaRank(moveList[moveNum], 4); // and back
5126       } else
5127       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5128        * the engine. It would be nice to have a better way to identify castle
5129        * moves here. */
5130       if(appData.fischerCastling && cps->useOOCastle) {
5131         int fromX = moveList[moveNum][0] - AAA;
5132         int fromY = moveList[moveNum][1] - ONE;
5133         int toX = moveList[moveNum][2] - AAA;
5134         int toY = moveList[moveNum][3] - ONE;
5135         if((boards[moveNum][fromY][fromX] == WhiteKing
5136             && boards[moveNum][toY][toX] == WhiteRook)
5137            || (boards[moveNum][fromY][fromX] == BlackKing
5138                && boards[moveNum][toY][toX] == BlackRook)) {
5139           if(toX > fromX) SendToProgram("O-O\n", cps);
5140           else SendToProgram("O-O-O\n", cps);
5141         }
5142         else SendToProgram(moveList[moveNum], cps);
5143       } else
5144       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5145         char *m = moveList[moveNum];
5146         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
5147           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5148                                                m[2], m[3] - '0',
5149                                                m[5], m[6] - '0',
5150                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5151         else
5152           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5153                                                m[5], m[6] - '0',
5154                                                m[5], m[6] - '0',
5155                                                m[2], m[3] - '0');
5156           SendToProgram(buf, cps);
5157       } else
5158       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5159         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5160           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5161           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5162                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5163         } else
5164           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5165                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5166         SendToProgram(buf, cps);
5167       }
5168       else SendToProgram(moveList[moveNum], cps);
5169       /* End of additions by Tord */
5170     }
5171
5172     /* [HGM] setting up the opening has brought engine in force mode! */
5173     /*       Send 'go' if we are in a mode where machine should play. */
5174     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5175         (gameMode == TwoMachinesPlay   ||
5176 #if ZIPPY
5177          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5178 #endif
5179          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5180         SendToProgram("go\n", cps);
5181   if (appData.debugMode) {
5182     fprintf(debugFP, "(extra)\n");
5183   }
5184     }
5185     setboardSpoiledMachineBlack = 0;
5186 }
5187
5188 void
5189 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5190 {
5191     char user_move[MSG_SIZ];
5192     char suffix[4];
5193
5194     if(gameInfo.variant == VariantSChess && promoChar) {
5195         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5196         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5197     } else suffix[0] = NULLCHAR;
5198
5199     switch (moveType) {
5200       default:
5201         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5202                 (int)moveType, fromX, fromY, toX, toY);
5203         DisplayError(user_move + strlen("say "), 0);
5204         break;
5205       case WhiteKingSideCastle:
5206       case BlackKingSideCastle:
5207       case WhiteQueenSideCastleWild:
5208       case BlackQueenSideCastleWild:
5209       /* PUSH Fabien */
5210       case WhiteHSideCastleFR:
5211       case BlackHSideCastleFR:
5212       /* POP Fabien */
5213         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5214         break;
5215       case WhiteQueenSideCastle:
5216       case BlackQueenSideCastle:
5217       case WhiteKingSideCastleWild:
5218       case BlackKingSideCastleWild:
5219       /* PUSH Fabien */
5220       case WhiteASideCastleFR:
5221       case BlackASideCastleFR:
5222       /* POP Fabien */
5223         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5224         break;
5225       case WhiteNonPromotion:
5226       case BlackNonPromotion:
5227         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5228         break;
5229       case WhitePromotion:
5230       case BlackPromotion:
5231         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5232            gameInfo.variant == VariantMakruk)
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 PieceToChar(WhiteFerz));
5236         else if(gameInfo.variant == VariantGreat)
5237           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5238                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5239                 PieceToChar(WhiteMan));
5240         else
5241           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5242                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5243                 promoChar);
5244         break;
5245       case WhiteDrop:
5246       case BlackDrop:
5247       drop:
5248         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5249                  ToUpper(PieceToChar((ChessSquare) fromX)),
5250                  AAA + toX, ONE + toY);
5251         break;
5252       case IllegalMove:  /* could be a variant we don't quite understand */
5253         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5254       case NormalMove:
5255       case WhiteCapturesEnPassant:
5256       case BlackCapturesEnPassant:
5257         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5258                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5259         break;
5260     }
5261     SendToICS(user_move);
5262     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5263         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5264 }
5265
5266 void
5267 UploadGameEvent ()
5268 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5269     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5270     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5271     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5272       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5273       return;
5274     }
5275     if(gameMode != IcsExamining) { // is this ever not the case?
5276         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5277
5278         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5279           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5280         } else { // on FICS we must first go to general examine mode
5281           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5282         }
5283         if(gameInfo.variant != VariantNormal) {
5284             // try figure out wild number, as xboard names are not always valid on ICS
5285             for(i=1; i<=36; i++) {
5286               snprintf(buf, MSG_SIZ, "wild/%d", i);
5287                 if(StringToVariant(buf) == gameInfo.variant) break;
5288             }
5289             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5290             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5291             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5292         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5293         SendToICS(ics_prefix);
5294         SendToICS(buf);
5295         if(startedFromSetupPosition || backwardMostMove != 0) {
5296           fen = PositionToFEN(backwardMostMove, NULL, 1);
5297           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5298             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5299             SendToICS(buf);
5300           } else { // FICS: everything has to set by separate bsetup commands
5301             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5302             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5303             SendToICS(buf);
5304             if(!WhiteOnMove(backwardMostMove)) {
5305                 SendToICS("bsetup tomove black\n");
5306             }
5307             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5308             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5309             SendToICS(buf);
5310             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5311             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5312             SendToICS(buf);
5313             i = boards[backwardMostMove][EP_STATUS];
5314             if(i >= 0) { // set e.p.
5315               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5316                 SendToICS(buf);
5317             }
5318             bsetup++;
5319           }
5320         }
5321       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5322             SendToICS("bsetup done\n"); // switch to normal examining.
5323     }
5324     for(i = backwardMostMove; i<last; i++) {
5325         char buf[20];
5326         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5327         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5328             int len = strlen(moveList[i]);
5329             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5330             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5331         }
5332         SendToICS(buf);
5333     }
5334     SendToICS(ics_prefix);
5335     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5336 }
5337
5338 int killX = -1, killY = -1, kill2X, kill2Y; // [HGM] lion: used for passing e.p. capture square to MakeMove
5339 int legNr = 1;
5340
5341 void
5342 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5343 {
5344     if (rf == DROP_RANK) {
5345       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5346       sprintf(move, "%c@%c%c\n",
5347                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5348     } else {
5349         if (promoChar == 'x' || promoChar == NULLCHAR) {
5350           sprintf(move, "%c%c%c%c\n",
5351                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5352           if(killX >= 0 && killY >= 0) {
5353             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5354             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5355           }
5356         } else {
5357             sprintf(move, "%c%c%c%c%c\n",
5358                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5359         }
5360     }
5361 }
5362
5363 void
5364 ProcessICSInitScript (FILE *f)
5365 {
5366     char buf[MSG_SIZ];
5367
5368     while (fgets(buf, MSG_SIZ, f)) {
5369         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5370     }
5371
5372     fclose(f);
5373 }
5374
5375
5376 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5377 int dragging;
5378 static ClickType lastClickType;
5379
5380 int
5381 Partner (ChessSquare *p)
5382 { // change piece into promotion partner if one shogi-promotes to the other
5383   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5384   ChessSquare partner;
5385   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5386   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5387   *p = partner;
5388   return 1;
5389 }
5390
5391 void
5392 Sweep (int step)
5393 {
5394     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5395     static int toggleFlag;
5396     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5397     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5398     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5399     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5400     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5401     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5402     do {
5403         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5404         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5405         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5406         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5407         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5408         if(!step) step = -1;
5409     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5410             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5411             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5412             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5413     if(toX >= 0) {
5414         int victim = boards[currentMove][toY][toX];
5415         boards[currentMove][toY][toX] = promoSweep;
5416         DrawPosition(FALSE, boards[currentMove]);
5417         boards[currentMove][toY][toX] = victim;
5418     } else
5419     ChangeDragPiece(promoSweep);
5420 }
5421
5422 int
5423 PromoScroll (int x, int y)
5424 {
5425   int step = 0;
5426
5427   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5428   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5429   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5430   if(!step) return FALSE;
5431   lastX = x; lastY = y;
5432   if((promoSweep < BlackPawn) == flipView) step = -step;
5433   if(step > 0) selectFlag = 1;
5434   if(!selectFlag) Sweep(step);
5435   return FALSE;
5436 }
5437
5438 void
5439 NextPiece (int step)
5440 {
5441     ChessSquare piece = boards[currentMove][toY][toX];
5442     do {
5443         pieceSweep -= step;
5444         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5445         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5446         if(!step) step = -1;
5447     } while(PieceToChar(pieceSweep) == '.');
5448     boards[currentMove][toY][toX] = pieceSweep;
5449     DrawPosition(FALSE, boards[currentMove]);
5450     boards[currentMove][toY][toX] = piece;
5451 }
5452 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5453 void
5454 AlphaRank (char *move, int n)
5455 {
5456 //    char *p = move, c; int x, y;
5457
5458     if (appData.debugMode) {
5459         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5460     }
5461
5462     if(move[1]=='*' &&
5463        move[2]>='0' && move[2]<='9' &&
5464        move[3]>='a' && move[3]<='x'    ) {
5465         move[1] = '@';
5466         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5467         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5468     } else
5469     if(move[0]>='0' && move[0]<='9' &&
5470        move[1]>='a' && move[1]<='x' &&
5471        move[2]>='0' && move[2]<='9' &&
5472        move[3]>='a' && move[3]<='x'    ) {
5473         /* input move, Shogi -> normal */
5474         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5475         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5476         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5477         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5478     } else
5479     if(move[1]=='@' &&
5480        move[3]>='0' && move[3]<='9' &&
5481        move[2]>='a' && move[2]<='x'    ) {
5482         move[1] = '*';
5483         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5484         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5485     } else
5486     if(
5487        move[0]>='a' && move[0]<='x' &&
5488        move[3]>='0' && move[3]<='9' &&
5489        move[2]>='a' && move[2]<='x'    ) {
5490          /* output move, normal -> Shogi */
5491         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5492         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5493         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5494         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5495         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5496     }
5497     if (appData.debugMode) {
5498         fprintf(debugFP, "   out = '%s'\n", move);
5499     }
5500 }
5501
5502 char yy_textstr[8000];
5503
5504 /* Parser for moves from gnuchess, ICS, or user typein box */
5505 Boolean
5506 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5507 {
5508     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5509
5510     switch (*moveType) {
5511       case WhitePromotion:
5512       case BlackPromotion:
5513       case WhiteNonPromotion:
5514       case BlackNonPromotion:
5515       case NormalMove:
5516       case FirstLeg:
5517       case WhiteCapturesEnPassant:
5518       case BlackCapturesEnPassant:
5519       case WhiteKingSideCastle:
5520       case WhiteQueenSideCastle:
5521       case BlackKingSideCastle:
5522       case BlackQueenSideCastle:
5523       case WhiteKingSideCastleWild:
5524       case WhiteQueenSideCastleWild:
5525       case BlackKingSideCastleWild:
5526       case BlackQueenSideCastleWild:
5527       /* Code added by Tord: */
5528       case WhiteHSideCastleFR:
5529       case WhiteASideCastleFR:
5530       case BlackHSideCastleFR:
5531       case BlackASideCastleFR:
5532       /* End of code added by Tord */
5533       case IllegalMove:         /* bug or odd chess variant */
5534         if(currentMoveString[1] == '@') { // illegal drop
5535           *fromX = WhiteOnMove(moveNum) ?
5536             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5537             (int) CharToPiece(ToLower(currentMoveString[0]));
5538           goto drop;
5539         }
5540         *fromX = currentMoveString[0] - AAA;
5541         *fromY = currentMoveString[1] - ONE;
5542         *toX = currentMoveString[2] - AAA;
5543         *toY = currentMoveString[3] - ONE;
5544         *promoChar = currentMoveString[4];
5545         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5546             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5547     if (appData.debugMode) {
5548         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5549     }
5550             *fromX = *fromY = *toX = *toY = 0;
5551             return FALSE;
5552         }
5553         if (appData.testLegality) {
5554           return (*moveType != IllegalMove);
5555         } else {
5556           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5557                          // [HGM] lion: if this is a double move we are less critical
5558                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5559         }
5560
5561       case WhiteDrop:
5562       case BlackDrop:
5563         *fromX = *moveType == WhiteDrop ?
5564           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5565           (int) CharToPiece(ToLower(currentMoveString[0]));
5566       drop:
5567         *fromY = DROP_RANK;
5568         *toX = currentMoveString[2] - AAA;
5569         *toY = currentMoveString[3] - ONE;
5570         *promoChar = NULLCHAR;
5571         return TRUE;
5572
5573       case AmbiguousMove:
5574       case ImpossibleMove:
5575       case EndOfFile:
5576       case ElapsedTime:
5577       case Comment:
5578       case PGNTag:
5579       case NAG:
5580       case WhiteWins:
5581       case BlackWins:
5582       case GameIsDrawn:
5583       default:
5584     if (appData.debugMode) {
5585         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5586     }
5587         /* bug? */
5588         *fromX = *fromY = *toX = *toY = 0;
5589         *promoChar = NULLCHAR;
5590         return FALSE;
5591     }
5592 }
5593
5594 Boolean pushed = FALSE;
5595 char *lastParseAttempt;
5596
5597 void
5598 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5599 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5600   int fromX, fromY, toX, toY; char promoChar;
5601   ChessMove moveType;
5602   Boolean valid;
5603   int nr = 0;
5604
5605   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5606   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5607     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5608     pushed = TRUE;
5609   }
5610   endPV = forwardMostMove;
5611   do {
5612     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5613     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5614     lastParseAttempt = pv;
5615     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5616     if(!valid && nr == 0 &&
5617        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5618         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5619         // Hande case where played move is different from leading PV move
5620         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5621         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5622         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5623         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5624           endPV += 2; // if position different, keep this
5625           moveList[endPV-1][0] = fromX + AAA;
5626           moveList[endPV-1][1] = fromY + ONE;
5627           moveList[endPV-1][2] = toX + AAA;
5628           moveList[endPV-1][3] = toY + ONE;
5629           parseList[endPV-1][0] = NULLCHAR;
5630           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5631         }
5632       }
5633     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5634     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5635     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5636     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5637         valid++; // allow comments in PV
5638         continue;
5639     }
5640     nr++;
5641     if(endPV+1 > framePtr) break; // no space, truncate
5642     if(!valid) break;
5643     endPV++;
5644     CopyBoard(boards[endPV], boards[endPV-1]);
5645     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5646     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5647     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5648     CoordsToAlgebraic(boards[endPV - 1],
5649                              PosFlags(endPV - 1),
5650                              fromY, fromX, toY, toX, promoChar,
5651                              parseList[endPV - 1]);
5652   } while(valid);
5653   if(atEnd == 2) return; // used hidden, for PV conversion
5654   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5655   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5656   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5657                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5658   DrawPosition(TRUE, boards[currentMove]);
5659 }
5660
5661 int
5662 MultiPV (ChessProgramState *cps)
5663 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5664         int i;
5665         for(i=0; i<cps->nrOptions; i++)
5666             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5667                 return i;
5668         return -1;
5669 }
5670
5671 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5672
5673 Boolean
5674 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5675 {
5676         int startPV, multi, lineStart, origIndex = index;
5677         char *p, buf2[MSG_SIZ];
5678         ChessProgramState *cps = (pane ? &second : &first);
5679
5680         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5681         lastX = x; lastY = y;
5682         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5683         lineStart = startPV = index;
5684         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5685         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5686         index = startPV;
5687         do{ while(buf[index] && buf[index] != '\n') index++;
5688         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5689         buf[index] = 0;
5690         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5691                 int n = cps->option[multi].value;
5692                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5693                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5694                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5695                 cps->option[multi].value = n;
5696                 *start = *end = 0;
5697                 return FALSE;
5698         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5699                 ExcludeClick(origIndex - lineStart);
5700                 return FALSE;
5701         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5702                 Collapse(origIndex - lineStart);
5703                 return FALSE;
5704         }
5705         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5706         *start = startPV; *end = index-1;
5707         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5708         return TRUE;
5709 }
5710
5711 char *
5712 PvToSAN (char *pv)
5713 {
5714         static char buf[10*MSG_SIZ];
5715         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5716         *buf = NULLCHAR;
5717         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5718         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5719         for(i = forwardMostMove; i<endPV; i++){
5720             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5721             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5722             k += strlen(buf+k);
5723         }
5724         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5725         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5726         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5727         endPV = savedEnd;
5728         return buf;
5729 }
5730
5731 Boolean
5732 LoadPV (int x, int y)
5733 { // called on right mouse click to load PV
5734   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5735   lastX = x; lastY = y;
5736   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5737   extendGame = FALSE;
5738   return TRUE;
5739 }
5740
5741 void
5742 UnLoadPV ()
5743 {
5744   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5745   if(endPV < 0) return;
5746   if(appData.autoCopyPV) CopyFENToClipboard();
5747   endPV = -1;
5748   if(extendGame && currentMove > forwardMostMove) {
5749         Boolean saveAnimate = appData.animate;
5750         if(pushed) {
5751             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5752                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5753             } else storedGames--; // abandon shelved tail of original game
5754         }
5755         pushed = FALSE;
5756         forwardMostMove = currentMove;
5757         currentMove = oldFMM;
5758         appData.animate = FALSE;
5759         ToNrEvent(forwardMostMove);
5760         appData.animate = saveAnimate;
5761   }
5762   currentMove = forwardMostMove;
5763   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5764   ClearPremoveHighlights();
5765   DrawPosition(TRUE, boards[currentMove]);
5766 }
5767
5768 void
5769 MovePV (int x, int y, int h)
5770 { // step through PV based on mouse coordinates (called on mouse move)
5771   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5772
5773   // we must somehow check if right button is still down (might be released off board!)
5774   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5775   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5776   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5777   if(!step) return;
5778   lastX = x; lastY = y;
5779
5780   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5781   if(endPV < 0) return;
5782   if(y < margin) step = 1; else
5783   if(y > h - margin) step = -1;
5784   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5785   currentMove += step;
5786   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5787   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5788                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5789   DrawPosition(FALSE, boards[currentMove]);
5790 }
5791
5792
5793 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5794 // All positions will have equal probability, but the current method will not provide a unique
5795 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5796 #define DARK 1
5797 #define LITE 2
5798 #define ANY 3
5799
5800 int squaresLeft[4];
5801 int piecesLeft[(int)BlackPawn];
5802 int seed, nrOfShuffles;
5803
5804 void
5805 GetPositionNumber ()
5806 {       // sets global variable seed
5807         int i;
5808
5809         seed = appData.defaultFrcPosition;
5810         if(seed < 0) { // randomize based on time for negative FRC position numbers
5811                 for(i=0; i<50; i++) seed += random();
5812                 seed = random() ^ random() >> 8 ^ random() << 8;
5813                 if(seed<0) seed = -seed;
5814         }
5815 }
5816
5817 int
5818 put (Board board, int pieceType, int rank, int n, int shade)
5819 // put the piece on the (n-1)-th empty squares of the given shade
5820 {
5821         int i;
5822
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5824                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5825                         board[rank][i] = (ChessSquare) pieceType;
5826                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5827                         squaresLeft[ANY]--;
5828                         piecesLeft[pieceType]--;
5829                         return i;
5830                 }
5831         }
5832         return -1;
5833 }
5834
5835
5836 void
5837 AddOnePiece (Board board, int pieceType, int rank, int shade)
5838 // calculate where the next piece goes, (any empty square), and put it there
5839 {
5840         int i;
5841
5842         i = seed % squaresLeft[shade];
5843         nrOfShuffles *= squaresLeft[shade];
5844         seed /= squaresLeft[shade];
5845         put(board, pieceType, rank, i, shade);
5846 }
5847
5848 void
5849 AddTwoPieces (Board board, int pieceType, int rank)
5850 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5851 {
5852         int i, n=squaresLeft[ANY], j=n-1, k;
5853
5854         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5855         i = seed % k;  // pick one
5856         nrOfShuffles *= k;
5857         seed /= k;
5858         while(i >= j) i -= j--;
5859         j = n - 1 - j; i += j;
5860         put(board, pieceType, rank, j, ANY);
5861         put(board, pieceType, rank, i, ANY);
5862 }
5863
5864 void
5865 SetUpShuffle (Board board, int number)
5866 {
5867         int i, p, first=1;
5868
5869         GetPositionNumber(); nrOfShuffles = 1;
5870
5871         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5872         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5873         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5874
5875         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5876
5877         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5878             p = (int) board[0][i];
5879             if(p < (int) BlackPawn) piecesLeft[p] ++;
5880             board[0][i] = EmptySquare;
5881         }
5882
5883         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5884             // shuffles restricted to allow normal castling put KRR first
5885             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5886                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5887             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5888                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5889             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5890                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5891             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5892                 put(board, WhiteRook, 0, 0, ANY);
5893             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5894         }
5895
5896         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5897             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5898             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5899                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5900                 while(piecesLeft[p] >= 2) {
5901                     AddOnePiece(board, p, 0, LITE);
5902                     AddOnePiece(board, p, 0, DARK);
5903                 }
5904                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5905             }
5906
5907         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5908             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5909             // but we leave King and Rooks for last, to possibly obey FRC restriction
5910             if(p == (int)WhiteRook) continue;
5911             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5912             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5913         }
5914
5915         // now everything is placed, except perhaps King (Unicorn) and Rooks
5916
5917         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5918             // Last King gets castling rights
5919             while(piecesLeft[(int)WhiteUnicorn]) {
5920                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5921                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5922             }
5923
5924             while(piecesLeft[(int)WhiteKing]) {
5925                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5926                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5927             }
5928
5929
5930         } else {
5931             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5932             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5933         }
5934
5935         // Only Rooks can be left; simply place them all
5936         while(piecesLeft[(int)WhiteRook]) {
5937                 i = put(board, WhiteRook, 0, 0, ANY);
5938                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5939                         if(first) {
5940                                 first=0;
5941                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5942                         }
5943                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5944                 }
5945         }
5946         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5947             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5948         }
5949
5950         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5951 }
5952
5953 int
5954 ptclen (const char *s, char *escapes)
5955 {
5956     int n = 0;
5957     if(!*escapes) return strlen(s);
5958     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5959     return n;
5960 }
5961
5962 int
5963 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5964 /* [HGM] moved here from winboard.c because of its general usefulness */
5965 /*       Basically a safe strcpy that uses the last character as King */
5966 {
5967     int result = FALSE; int NrPieces;
5968
5969     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5970                     && NrPieces >= 12 && !(NrPieces&1)) {
5971         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5972
5973         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5974         for( i=0; i<NrPieces/2-1; i++ ) {
5975             char *p;
5976             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5977             table[i] = map[j++];
5978             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5979         }
5980         table[(int) WhiteKing]  = map[j++];
5981         for( i=0; i<NrPieces/2-1; i++ ) {
5982             char *p;
5983             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5984             table[WHITE_TO_BLACK i] = map[j++];
5985             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5986         }
5987         table[(int) BlackKing]  = map[j++];
5988
5989         result = TRUE;
5990     }
5991
5992     return result;
5993 }
5994
5995 int
5996 SetCharTable (unsigned char *table, const char * map)
5997 {
5998     return SetCharTableEsc(table, map, "");
5999 }
6000
6001 void
6002 Prelude (Board board)
6003 {       // [HGM] superchess: random selection of exo-pieces
6004         int i, j, k; ChessSquare p;
6005         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6006
6007         GetPositionNumber(); // use FRC position number
6008
6009         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6010             SetCharTable(pieceToChar, appData.pieceToCharTable);
6011             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6012                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6013         }
6014
6015         j = seed%4;                 seed /= 4;
6016         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6017         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6018         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6019         j = seed%3 + (seed%3 >= j); seed /= 3;
6020         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6021         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6022         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6023         j = seed%3;                 seed /= 3;
6024         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6025         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6026         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6027         j = seed%2 + (seed%2 >= j); seed /= 2;
6028         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6029         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6030         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6031         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6032         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6033         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6034         put(board, exoPieces[0],    0, 0, ANY);
6035         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6036 }
6037
6038 void
6039 InitPosition (int redraw)
6040 {
6041     ChessSquare (* pieces)[BOARD_FILES];
6042     int i, j, pawnRow=1, pieceRows=1, overrule,
6043     oldx = gameInfo.boardWidth,
6044     oldy = gameInfo.boardHeight,
6045     oldh = gameInfo.holdingsWidth;
6046     static int oldv;
6047
6048     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6049
6050     /* [AS] Initialize pv info list [HGM] and game status */
6051     {
6052         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6053             pvInfoList[i].depth = 0;
6054             boards[i][EP_STATUS] = EP_NONE;
6055             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6056         }
6057
6058         initialRulePlies = 0; /* 50-move counter start */
6059
6060         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6061         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6062     }
6063
6064
6065     /* [HGM] logic here is completely changed. In stead of full positions */
6066     /* the initialized data only consist of the two backranks. The switch */
6067     /* selects which one we will use, which is than copied to the Board   */
6068     /* initialPosition, which for the rest is initialized by Pawns and    */
6069     /* empty squares. This initial position is then copied to boards[0],  */
6070     /* possibly after shuffling, so that it remains available.            */
6071
6072     gameInfo.holdingsWidth = 0; /* default board sizes */
6073     gameInfo.boardWidth    = 8;
6074     gameInfo.boardHeight   = 8;
6075     gameInfo.holdingsSize  = 0;
6076     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6077     for(i=0; i<BOARD_FILES-6; i++)
6078       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6079     initialPosition[EP_STATUS] = EP_NONE;
6080     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6081     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6082     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6083          SetCharTable(pieceNickName, appData.pieceNickNames);
6084     else SetCharTable(pieceNickName, "............");
6085     pieces = FIDEArray;
6086
6087     switch (gameInfo.variant) {
6088     case VariantFischeRandom:
6089       shuffleOpenings = TRUE;
6090       appData.fischerCastling = TRUE;
6091     default:
6092       break;
6093     case VariantShatranj:
6094       pieces = ShatranjArray;
6095       nrCastlingRights = 0;
6096       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6097       break;
6098     case VariantMakruk:
6099       pieces = makrukArray;
6100       nrCastlingRights = 0;
6101       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6102       break;
6103     case VariantASEAN:
6104       pieces = aseanArray;
6105       nrCastlingRights = 0;
6106       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6107       break;
6108     case VariantTwoKings:
6109       pieces = twoKingsArray;
6110       break;
6111     case VariantGrand:
6112       pieces = GrandArray;
6113       nrCastlingRights = 0;
6114       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6115       gameInfo.boardWidth = 10;
6116       gameInfo.boardHeight = 10;
6117       gameInfo.holdingsSize = 7;
6118       break;
6119     case VariantCapaRandom:
6120       shuffleOpenings = TRUE;
6121       appData.fischerCastling = TRUE;
6122     case VariantCapablanca:
6123       pieces = CapablancaArray;
6124       gameInfo.boardWidth = 10;
6125       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6126       break;
6127     case VariantGothic:
6128       pieces = GothicArray;
6129       gameInfo.boardWidth = 10;
6130       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6131       break;
6132     case VariantSChess:
6133       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6134       gameInfo.holdingsSize = 7;
6135       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6136       break;
6137     case VariantJanus:
6138       pieces = JanusArray;
6139       gameInfo.boardWidth = 10;
6140       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6141       nrCastlingRights = 6;
6142         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6143         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6144         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6145         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6146         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6147         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6148       break;
6149     case VariantFalcon:
6150       pieces = FalconArray;
6151       gameInfo.boardWidth = 10;
6152       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6153       break;
6154     case VariantXiangqi:
6155       pieces = XiangqiArray;
6156       gameInfo.boardWidth  = 9;
6157       gameInfo.boardHeight = 10;
6158       nrCastlingRights = 0;
6159       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6160       break;
6161     case VariantShogi:
6162       pieces = ShogiArray;
6163       gameInfo.boardWidth  = 9;
6164       gameInfo.boardHeight = 9;
6165       gameInfo.holdingsSize = 7;
6166       nrCastlingRights = 0;
6167       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6168       break;
6169     case VariantChu:
6170       pieces = ChuArray; pieceRows = 3;
6171       gameInfo.boardWidth  = 12;
6172       gameInfo.boardHeight = 12;
6173       nrCastlingRights = 0;
6174       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6175                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6176       break;
6177     case VariantCourier:
6178       pieces = CourierArray;
6179       gameInfo.boardWidth  = 12;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6182       break;
6183     case VariantKnightmate:
6184       pieces = KnightmateArray;
6185       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6186       break;
6187     case VariantSpartan:
6188       pieces = SpartanArray;
6189       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6190       break;
6191     case VariantLion:
6192       pieces = lionArray;
6193       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6194       break;
6195     case VariantChuChess:
6196       pieces = ChuChessArray;
6197       gameInfo.boardWidth = 10;
6198       gameInfo.boardHeight = 10;
6199       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6200       break;
6201     case VariantFairy:
6202       pieces = fairyArray;
6203       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6204       break;
6205     case VariantGreat:
6206       pieces = GreatArray;
6207       gameInfo.boardWidth = 10;
6208       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6209       gameInfo.holdingsSize = 8;
6210       break;
6211     case VariantSuper:
6212       pieces = FIDEArray;
6213       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6214       gameInfo.holdingsSize = 8;
6215       startedFromSetupPosition = TRUE;
6216       break;
6217     case VariantCrazyhouse:
6218     case VariantBughouse:
6219       pieces = FIDEArray;
6220       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6221       gameInfo.holdingsSize = 5;
6222       break;
6223     case VariantWildCastle:
6224       pieces = FIDEArray;
6225       /* !!?shuffle with kings guaranteed to be on d or e file */
6226       shuffleOpenings = 1;
6227       break;
6228     case VariantNoCastle:
6229       pieces = FIDEArray;
6230       nrCastlingRights = 0;
6231       /* !!?unconstrained back-rank shuffle */
6232       shuffleOpenings = 1;
6233       break;
6234     }
6235
6236     overrule = 0;
6237     if(appData.NrFiles >= 0) {
6238         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6239         gameInfo.boardWidth = appData.NrFiles;
6240     }
6241     if(appData.NrRanks >= 0) {
6242         gameInfo.boardHeight = appData.NrRanks;
6243     }
6244     if(appData.holdingsSize >= 0) {
6245         i = appData.holdingsSize;
6246         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6247         gameInfo.holdingsSize = i;
6248     }
6249     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6250     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6251         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6252
6253     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6254     if(pawnRow < 1) pawnRow = 1;
6255     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6256        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6257     if(gameInfo.variant == VariantChu) pawnRow = 3;
6258
6259     /* User pieceToChar list overrules defaults */
6260     if(appData.pieceToCharTable != NULL)
6261         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6262
6263     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6264
6265         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6266             s = (ChessSquare) 0; /* account holding counts in guard band */
6267         for( i=0; i<BOARD_HEIGHT; i++ )
6268             initialPosition[i][j] = s;
6269
6270         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6271         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6272         initialPosition[pawnRow][j] = WhitePawn;
6273         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6274         if(gameInfo.variant == VariantXiangqi) {
6275             if(j&1) {
6276                 initialPosition[pawnRow][j] =
6277                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6278                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6279                    initialPosition[2][j] = WhiteCannon;
6280                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6281                 }
6282             }
6283         }
6284         if(gameInfo.variant == VariantChu) {
6285              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6286                initialPosition[pawnRow+1][j] = WhiteCobra,
6287                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6288              for(i=1; i<pieceRows; i++) {
6289                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6290                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6291              }
6292         }
6293         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6294             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6295                initialPosition[0][j] = WhiteRook;
6296                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6297             }
6298         }
6299         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6300     }
6301     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6302     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6303
6304             j=BOARD_LEFT+1;
6305             initialPosition[1][j] = WhiteBishop;
6306             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6307             j=BOARD_RGHT-2;
6308             initialPosition[1][j] = WhiteRook;
6309             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6310     }
6311
6312     if( nrCastlingRights == -1) {
6313         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6314         /*       This sets default castling rights from none to normal corners   */
6315         /* Variants with other castling rights must set them themselves above    */
6316         nrCastlingRights = 6;
6317
6318         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6319         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6320         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6321         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6322         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6323         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6324      }
6325
6326      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6327      if(gameInfo.variant == VariantGreat) { // promotion commoners
6328         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6329         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6330         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6331         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6332      }
6333      if( gameInfo.variant == VariantSChess ) {
6334       initialPosition[1][0] = BlackMarshall;
6335       initialPosition[2][0] = BlackAngel;
6336       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6337       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6338       initialPosition[1][1] = initialPosition[2][1] =
6339       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6340      }
6341   if (appData.debugMode) {
6342     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6343   }
6344     if(shuffleOpenings) {
6345         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6346         startedFromSetupPosition = TRUE;
6347     }
6348     if(startedFromPositionFile) {
6349       /* [HGM] loadPos: use PositionFile for every new game */
6350       CopyBoard(initialPosition, filePosition);
6351       for(i=0; i<nrCastlingRights; i++)
6352           initialRights[i] = filePosition[CASTLING][i];
6353       startedFromSetupPosition = TRUE;
6354     }
6355
6356     CopyBoard(boards[0], initialPosition);
6357
6358     if(oldx != gameInfo.boardWidth ||
6359        oldy != gameInfo.boardHeight ||
6360        oldv != gameInfo.variant ||
6361        oldh != gameInfo.holdingsWidth
6362                                          )
6363             InitDrawingSizes(-2 ,0);
6364
6365     oldv = gameInfo.variant;
6366     if (redraw)
6367       DrawPosition(TRUE, boards[currentMove]);
6368 }
6369
6370 void
6371 SendBoard (ChessProgramState *cps, int moveNum)
6372 {
6373     char message[MSG_SIZ];
6374
6375     if (cps->useSetboard) {
6376       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6377       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6378       SendToProgram(message, cps);
6379       free(fen);
6380
6381     } else {
6382       ChessSquare *bp;
6383       int i, j, left=0, right=BOARD_WIDTH;
6384       /* Kludge to set black to move, avoiding the troublesome and now
6385        * deprecated "black" command.
6386        */
6387       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6388         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6389
6390       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6391
6392       SendToProgram("edit\n", cps);
6393       SendToProgram("#\n", cps);
6394       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6395         bp = &boards[moveNum][i][left];
6396         for (j = left; j < right; j++, bp++) {
6397           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6398           if ((int) *bp < (int) BlackPawn) {
6399             if(j == BOARD_RGHT+1)
6400                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6401             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6402             if(message[0] == '+' || message[0] == '~') {
6403               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6404                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6405                         AAA + j, ONE + i);
6406             }
6407             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6408                 message[1] = BOARD_RGHT   - 1 - j + '1';
6409                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6410             }
6411             SendToProgram(message, cps);
6412           }
6413         }
6414       }
6415
6416       SendToProgram("c\n", cps);
6417       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6418         bp = &boards[moveNum][i][left];
6419         for (j = left; j < right; j++, bp++) {
6420           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6421           if (((int) *bp != (int) EmptySquare)
6422               && ((int) *bp >= (int) BlackPawn)) {
6423             if(j == BOARD_LEFT-2)
6424                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6425             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6426                     AAA + j, ONE + i);
6427             if(message[0] == '+' || message[0] == '~') {
6428               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6429                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6430                         AAA + j, ONE + i);
6431             }
6432             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6433                 message[1] = BOARD_RGHT   - 1 - j + '1';
6434                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6435             }
6436             SendToProgram(message, cps);
6437           }
6438         }
6439       }
6440
6441       SendToProgram(".\n", cps);
6442     }
6443     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6444 }
6445
6446 char exclusionHeader[MSG_SIZ];
6447 int exCnt, excludePtr;
6448 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6449 static Exclusion excluTab[200];
6450 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6451
6452 static void
6453 WriteMap (int s)
6454 {
6455     int j;
6456     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6457     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6458 }
6459
6460 static void
6461 ClearMap ()
6462 {
6463     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6464     excludePtr = 24; exCnt = 0;
6465     WriteMap(0);
6466 }
6467
6468 static void
6469 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6470 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6471     char buf[2*MOVE_LEN], *p;
6472     Exclusion *e = excluTab;
6473     int i;
6474     for(i=0; i<exCnt; i++)
6475         if(e[i].ff == fromX && e[i].fr == fromY &&
6476            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6477     if(i == exCnt) { // was not in exclude list; add it
6478         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6479         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6480             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6481             return; // abort
6482         }
6483         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6484         excludePtr++; e[i].mark = excludePtr++;
6485         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6486         exCnt++;
6487     }
6488     exclusionHeader[e[i].mark] = state;
6489 }
6490
6491 static int
6492 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6493 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6494     char buf[MSG_SIZ];
6495     int j, k;
6496     ChessMove moveType;
6497     if((signed char)promoChar == -1) { // kludge to indicate best move
6498         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6499             return 1; // if unparsable, abort
6500     }
6501     // update exclusion map (resolving toggle by consulting existing state)
6502     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6503     j = k%8; k >>= 3;
6504     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6505     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6506          excludeMap[k] |=   1<<j;
6507     else excludeMap[k] &= ~(1<<j);
6508     // update header
6509     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6510     // inform engine
6511     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6512     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6513     SendToBoth(buf);
6514     return (state == '+');
6515 }
6516
6517 static void
6518 ExcludeClick (int index)
6519 {
6520     int i, j;
6521     Exclusion *e = excluTab;
6522     if(index < 25) { // none, best or tail clicked
6523         if(index < 13) { // none: include all
6524             WriteMap(0); // clear map
6525             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6526             SendToBoth("include all\n"); // and inform engine
6527         } else if(index > 18) { // tail
6528             if(exclusionHeader[19] == '-') { // tail was excluded
6529                 SendToBoth("include all\n");
6530                 WriteMap(0); // clear map completely
6531                 // now re-exclude selected moves
6532                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6533                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6534             } else { // tail was included or in mixed state
6535                 SendToBoth("exclude all\n");
6536                 WriteMap(0xFF); // fill map completely
6537                 // now re-include selected moves
6538                 j = 0; // count them
6539                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6540                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6541                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6542             }
6543         } else { // best
6544             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6545         }
6546     } else {
6547         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6548             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6549             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6550             break;
6551         }
6552     }
6553 }
6554
6555 ChessSquare
6556 DefaultPromoChoice (int white)
6557 {
6558     ChessSquare result;
6559     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6560        gameInfo.variant == VariantMakruk)
6561         result = WhiteFerz; // no choice
6562     else if(gameInfo.variant == VariantASEAN)
6563         result = WhiteRook; // no choice
6564     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6565         result= WhiteKing; // in Suicide Q is the last thing we want
6566     else if(gameInfo.variant == VariantSpartan)
6567         result = white ? WhiteQueen : WhiteAngel;
6568     else result = WhiteQueen;
6569     if(!white) result = WHITE_TO_BLACK result;
6570     return result;
6571 }
6572
6573 static int autoQueen; // [HGM] oneclick
6574
6575 int
6576 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6577 {
6578     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6579     /* [HGM] add Shogi promotions */
6580     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6581     ChessSquare piece, partner;
6582     ChessMove moveType;
6583     Boolean premove;
6584
6585     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6586     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6587
6588     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6589       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6590         return FALSE;
6591
6592     piece = boards[currentMove][fromY][fromX];
6593     if(gameInfo.variant == VariantChu) {
6594         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6595         promotionZoneSize = BOARD_HEIGHT/3;
6596         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6597     } else if(gameInfo.variant == VariantShogi) {
6598         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6599         highestPromotingPiece = (int)WhiteAlfil;
6600     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6601         promotionZoneSize = 3;
6602     }
6603
6604     // Treat Lance as Pawn when it is not representing Amazon or Lance
6605     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6606         if(piece == WhiteLance) piece = WhitePawn; else
6607         if(piece == BlackLance) piece = BlackPawn;
6608     }
6609
6610     // next weed out all moves that do not touch the promotion zone at all
6611     if((int)piece >= BlackPawn) {
6612         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6613              return FALSE;
6614         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6615         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6616     } else {
6617         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6618            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6619         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6620              return FALSE;
6621     }
6622
6623     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6624
6625     // weed out mandatory Shogi promotions
6626     if(gameInfo.variant == VariantShogi) {
6627         if(piece >= BlackPawn) {
6628             if(toY == 0 && piece == BlackPawn ||
6629                toY == 0 && piece == BlackQueen ||
6630                toY <= 1 && piece == BlackKnight) {
6631                 *promoChoice = '+';
6632                 return FALSE;
6633             }
6634         } else {
6635             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6636                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6637                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6638                 *promoChoice = '+';
6639                 return FALSE;
6640             }
6641         }
6642     }
6643
6644     // weed out obviously illegal Pawn moves
6645     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6646         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6647         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6648         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6649         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6650         // note we are not allowed to test for valid (non-)capture, due to premove
6651     }
6652
6653     // we either have a choice what to promote to, or (in Shogi) whether to promote
6654     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6655        gameInfo.variant == VariantMakruk) {
6656         ChessSquare p=BlackFerz;  // no choice
6657         while(p < EmptySquare) {  //but make sure we use piece that exists
6658             *promoChoice = PieceToChar(p++);
6659             if(*promoChoice != '.') break;
6660         }
6661         return FALSE;
6662     }
6663     // no sense asking what we must promote to if it is going to explode...
6664     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6665         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6666         return FALSE;
6667     }
6668     // give caller the default choice even if we will not make it
6669     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6670     partner = piece; // pieces can promote if the pieceToCharTable says so
6671     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6672     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6673     if(        sweepSelect && gameInfo.variant != VariantGreat
6674                            && gameInfo.variant != VariantGrand
6675                            && gameInfo.variant != VariantSuper) return FALSE;
6676     if(autoQueen) return FALSE; // predetermined
6677
6678     // suppress promotion popup on illegal moves that are not premoves
6679     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6680               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6681     if(appData.testLegality && !premove) {
6682         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6683                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6684         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6685         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6686             return FALSE;
6687     }
6688
6689     return TRUE;
6690 }
6691
6692 int
6693 InPalace (int row, int column)
6694 {   /* [HGM] for Xiangqi */
6695     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6696          column < (BOARD_WIDTH + 4)/2 &&
6697          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6698     return FALSE;
6699 }
6700
6701 int
6702 PieceForSquare (int x, int y)
6703 {
6704   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6705      return -1;
6706   else
6707      return boards[currentMove][y][x];
6708 }
6709
6710 int
6711 OKToStartUserMove (int x, int y)
6712 {
6713     ChessSquare from_piece;
6714     int white_piece;
6715
6716     if (matchMode) return FALSE;
6717     if (gameMode == EditPosition) return TRUE;
6718
6719     if (x >= 0 && y >= 0)
6720       from_piece = boards[currentMove][y][x];
6721     else
6722       from_piece = EmptySquare;
6723
6724     if (from_piece == EmptySquare) return FALSE;
6725
6726     white_piece = (int)from_piece >= (int)WhitePawn &&
6727       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6728
6729     switch (gameMode) {
6730       case AnalyzeFile:
6731       case TwoMachinesPlay:
6732       case EndOfGame:
6733         return FALSE;
6734
6735       case IcsObserving:
6736       case IcsIdle:
6737         return FALSE;
6738
6739       case MachinePlaysWhite:
6740       case IcsPlayingBlack:
6741         if (appData.zippyPlay) return FALSE;
6742         if (white_piece) {
6743             DisplayMoveError(_("You are playing Black"));
6744             return FALSE;
6745         }
6746         break;
6747
6748       case MachinePlaysBlack:
6749       case IcsPlayingWhite:
6750         if (appData.zippyPlay) return FALSE;
6751         if (!white_piece) {
6752             DisplayMoveError(_("You are playing White"));
6753             return FALSE;
6754         }
6755         break;
6756
6757       case PlayFromGameFile:
6758             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6759       case EditGame:
6760         if (!white_piece && WhiteOnMove(currentMove)) {
6761             DisplayMoveError(_("It is White's turn"));
6762             return FALSE;
6763         }
6764         if (white_piece && !WhiteOnMove(currentMove)) {
6765             DisplayMoveError(_("It is Black's turn"));
6766             return FALSE;
6767         }
6768         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6769             /* Editing correspondence game history */
6770             /* Could disallow this or prompt for confirmation */
6771             cmailOldMove = -1;
6772         }
6773         break;
6774
6775       case BeginningOfGame:
6776         if (appData.icsActive) return FALSE;
6777         if (!appData.noChessProgram) {
6778             if (!white_piece) {
6779                 DisplayMoveError(_("You are playing White"));
6780                 return FALSE;
6781             }
6782         }
6783         break;
6784
6785       case Training:
6786         if (!white_piece && WhiteOnMove(currentMove)) {
6787             DisplayMoveError(_("It is White's turn"));
6788             return FALSE;
6789         }
6790         if (white_piece && !WhiteOnMove(currentMove)) {
6791             DisplayMoveError(_("It is Black's turn"));
6792             return FALSE;
6793         }
6794         break;
6795
6796       default:
6797       case IcsExamining:
6798         break;
6799     }
6800     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6801         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6802         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6803         && gameMode != AnalyzeFile && gameMode != Training) {
6804         DisplayMoveError(_("Displayed position is not current"));
6805         return FALSE;
6806     }
6807     return TRUE;
6808 }
6809
6810 Boolean
6811 OnlyMove (int *x, int *y, Boolean captures)
6812 {
6813     DisambiguateClosure cl;
6814     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6815     switch(gameMode) {
6816       case MachinePlaysBlack:
6817       case IcsPlayingWhite:
6818       case BeginningOfGame:
6819         if(!WhiteOnMove(currentMove)) return FALSE;
6820         break;
6821       case MachinePlaysWhite:
6822       case IcsPlayingBlack:
6823         if(WhiteOnMove(currentMove)) return FALSE;
6824         break;
6825       case EditGame:
6826         break;
6827       default:
6828         return FALSE;
6829     }
6830     cl.pieceIn = EmptySquare;
6831     cl.rfIn = *y;
6832     cl.ffIn = *x;
6833     cl.rtIn = -1;
6834     cl.ftIn = -1;
6835     cl.promoCharIn = NULLCHAR;
6836     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6837     if( cl.kind == NormalMove ||
6838         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6839         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6840         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6841       fromX = cl.ff;
6842       fromY = cl.rf;
6843       *x = cl.ft;
6844       *y = cl.rt;
6845       return TRUE;
6846     }
6847     if(cl.kind != ImpossibleMove) return FALSE;
6848     cl.pieceIn = EmptySquare;
6849     cl.rfIn = -1;
6850     cl.ffIn = -1;
6851     cl.rtIn = *y;
6852     cl.ftIn = *x;
6853     cl.promoCharIn = NULLCHAR;
6854     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6855     if( cl.kind == NormalMove ||
6856         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6857         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6858         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6859       fromX = cl.ff;
6860       fromY = cl.rf;
6861       *x = cl.ft;
6862       *y = cl.rt;
6863       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6864       return TRUE;
6865     }
6866     return FALSE;
6867 }
6868
6869 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6870 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6871 int lastLoadGameUseList = FALSE;
6872 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6873 ChessMove lastLoadGameStart = EndOfFile;
6874 int doubleClick;
6875 Boolean addToBookFlag;
6876
6877 void
6878 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6879 {
6880     ChessMove moveType;
6881     ChessSquare pup;
6882     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6883
6884     /* Check if the user is playing in turn.  This is complicated because we
6885        let the user "pick up" a piece before it is his turn.  So the piece he
6886        tried to pick up may have been captured by the time he puts it down!
6887        Therefore we use the color the user is supposed to be playing in this
6888        test, not the color of the piece that is currently on the starting
6889        square---except in EditGame mode, where the user is playing both
6890        sides; fortunately there the capture race can't happen.  (It can
6891        now happen in IcsExamining mode, but that's just too bad.  The user
6892        will get a somewhat confusing message in that case.)
6893        */
6894
6895     switch (gameMode) {
6896       case AnalyzeFile:
6897       case TwoMachinesPlay:
6898       case EndOfGame:
6899       case IcsObserving:
6900       case IcsIdle:
6901         /* We switched into a game mode where moves are not accepted,
6902            perhaps while the mouse button was down. */
6903         return;
6904
6905       case MachinePlaysWhite:
6906         /* User is moving for Black */
6907         if (WhiteOnMove(currentMove)) {
6908             DisplayMoveError(_("It is White's turn"));
6909             return;
6910         }
6911         break;
6912
6913       case MachinePlaysBlack:
6914         /* User is moving for White */
6915         if (!WhiteOnMove(currentMove)) {
6916             DisplayMoveError(_("It is Black's turn"));
6917             return;
6918         }
6919         break;
6920
6921       case PlayFromGameFile:
6922             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6923       case EditGame:
6924       case IcsExamining:
6925       case BeginningOfGame:
6926       case AnalyzeMode:
6927       case Training:
6928         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6929         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6930             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6931             /* User is moving for Black */
6932             if (WhiteOnMove(currentMove)) {
6933                 DisplayMoveError(_("It is White's turn"));
6934                 return;
6935             }
6936         } else {
6937             /* User is moving for White */
6938             if (!WhiteOnMove(currentMove)) {
6939                 DisplayMoveError(_("It is Black's turn"));
6940                 return;
6941             }
6942         }
6943         break;
6944
6945       case IcsPlayingBlack:
6946         /* User is moving for Black */
6947         if (WhiteOnMove(currentMove)) {
6948             if (!appData.premove) {
6949                 DisplayMoveError(_("It is White's turn"));
6950             } else if (toX >= 0 && toY >= 0) {
6951                 premoveToX = toX;
6952                 premoveToY = toY;
6953                 premoveFromX = fromX;
6954                 premoveFromY = fromY;
6955                 premovePromoChar = promoChar;
6956                 gotPremove = 1;
6957                 if (appData.debugMode)
6958                     fprintf(debugFP, "Got premove: fromX %d,"
6959                             "fromY %d, toX %d, toY %d\n",
6960                             fromX, fromY, toX, toY);
6961             }
6962             return;
6963         }
6964         break;
6965
6966       case IcsPlayingWhite:
6967         /* User is moving for White */
6968         if (!WhiteOnMove(currentMove)) {
6969             if (!appData.premove) {
6970                 DisplayMoveError(_("It is Black's turn"));
6971             } else if (toX >= 0 && toY >= 0) {
6972                 premoveToX = toX;
6973                 premoveToY = toY;
6974                 premoveFromX = fromX;
6975                 premoveFromY = fromY;
6976                 premovePromoChar = promoChar;
6977                 gotPremove = 1;
6978                 if (appData.debugMode)
6979                     fprintf(debugFP, "Got premove: fromX %d,"
6980                             "fromY %d, toX %d, toY %d\n",
6981                             fromX, fromY, toX, toY);
6982             }
6983             return;
6984         }
6985         break;
6986
6987       default:
6988         break;
6989
6990       case EditPosition:
6991         /* EditPosition, empty square, or different color piece;
6992            click-click move is possible */
6993         if (toX == -2 || toY == -2) {
6994             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6995             DrawPosition(FALSE, boards[currentMove]);
6996             return;
6997         } else if (toX >= 0 && toY >= 0) {
6998             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6999                 ChessSquare q, p = boards[0][rf][ff];
7000                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7001                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7002                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7003                 if(PieceToChar(q) == '+') gatingPiece = p;
7004             }
7005             boards[0][toY][toX] = boards[0][fromY][fromX];
7006             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7007                 if(boards[0][fromY][0] != EmptySquare) {
7008                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7009                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7010                 }
7011             } else
7012             if(fromX == BOARD_RGHT+1) {
7013                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7014                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7015                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7016                 }
7017             } else
7018             boards[0][fromY][fromX] = gatingPiece;
7019             DrawPosition(FALSE, boards[currentMove]);
7020             return;
7021         }
7022         return;
7023     }
7024
7025     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7026     pup = boards[currentMove][toY][toX];
7027
7028     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7029     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7030          if( pup != EmptySquare ) return;
7031          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7032            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7033                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7034            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7035            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7036            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7037            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7038          fromY = DROP_RANK;
7039     }
7040
7041     /* [HGM] always test for legality, to get promotion info */
7042     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7043                                          fromY, fromX, toY, toX, promoChar);
7044
7045     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7046
7047     /* [HGM] but possibly ignore an IllegalMove result */
7048     if (appData.testLegality) {
7049         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7050             DisplayMoveError(_("Illegal move"));
7051             return;
7052         }
7053     }
7054
7055     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7056         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7057              ClearPremoveHighlights(); // was included
7058         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7059         return;
7060     }
7061
7062     if(addToBookFlag) { // adding moves to book
7063         char buf[MSG_SIZ], move[MSG_SIZ];
7064         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7065         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');
7066         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7067         AddBookMove(buf);
7068         addToBookFlag = FALSE;
7069         ClearHighlights();
7070         return;
7071     }
7072
7073     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7074 }
7075
7076 /* Common tail of UserMoveEvent and DropMenuEvent */
7077 int
7078 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7079 {
7080     char *bookHit = 0;
7081
7082     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7083         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7084         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7085         if(WhiteOnMove(currentMove)) {
7086             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7087         } else {
7088             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7089         }
7090     }
7091
7092     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7093        move type in caller when we know the move is a legal promotion */
7094     if(moveType == NormalMove && promoChar)
7095         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7096
7097     /* [HGM] <popupFix> The following if has been moved here from
7098        UserMoveEvent(). Because it seemed to belong here (why not allow
7099        piece drops in training games?), and because it can only be
7100        performed after it is known to what we promote. */
7101     if (gameMode == Training) {
7102       /* compare the move played on the board to the next move in the
7103        * game. If they match, display the move and the opponent's response.
7104        * If they don't match, display an error message.
7105        */
7106       int saveAnimate;
7107       Board testBoard;
7108       CopyBoard(testBoard, boards[currentMove]);
7109       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7110
7111       if (CompareBoards(testBoard, boards[currentMove+1])) {
7112         ForwardInner(currentMove+1);
7113
7114         /* Autoplay the opponent's response.
7115          * if appData.animate was TRUE when Training mode was entered,
7116          * the response will be animated.
7117          */
7118         saveAnimate = appData.animate;
7119         appData.animate = animateTraining;
7120         ForwardInner(currentMove+1);
7121         appData.animate = saveAnimate;
7122
7123         /* check for the end of the game */
7124         if (currentMove >= forwardMostMove) {
7125           gameMode = PlayFromGameFile;
7126           ModeHighlight();
7127           SetTrainingModeOff();
7128           DisplayInformation(_("End of game"));
7129         }
7130       } else {
7131         DisplayError(_("Incorrect move"), 0);
7132       }
7133       return 1;
7134     }
7135
7136   /* Ok, now we know that the move is good, so we can kill
7137      the previous line in Analysis Mode */
7138   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7139                                 && currentMove < forwardMostMove) {
7140     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7141     else forwardMostMove = currentMove;
7142   }
7143
7144   ClearMap();
7145
7146   /* If we need the chess program but it's dead, restart it */
7147   ResurrectChessProgram();
7148
7149   /* A user move restarts a paused game*/
7150   if (pausing)
7151     PauseEvent();
7152
7153   thinkOutput[0] = NULLCHAR;
7154
7155   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7156
7157   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7158     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7159     return 1;
7160   }
7161
7162   if (gameMode == BeginningOfGame) {
7163     if (appData.noChessProgram) {
7164       gameMode = EditGame;
7165       SetGameInfo();
7166     } else {
7167       char buf[MSG_SIZ];
7168       gameMode = MachinePlaysBlack;
7169       StartClocks();
7170       SetGameInfo();
7171       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7172       DisplayTitle(buf);
7173       if (first.sendName) {
7174         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7175         SendToProgram(buf, &first);
7176       }
7177       StartClocks();
7178     }
7179     ModeHighlight();
7180   }
7181
7182   /* Relay move to ICS or chess engine */
7183   if (appData.icsActive) {
7184     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7185         gameMode == IcsExamining) {
7186       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7187         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7188         SendToICS("draw ");
7189         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7190       }
7191       // also send plain move, in case ICS does not understand atomic claims
7192       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7193       ics_user_moved = 1;
7194     }
7195   } else {
7196     if (first.sendTime && (gameMode == BeginningOfGame ||
7197                            gameMode == MachinePlaysWhite ||
7198                            gameMode == MachinePlaysBlack)) {
7199       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7200     }
7201     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7202          // [HGM] book: if program might be playing, let it use book
7203         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7204         first.maybeThinking = TRUE;
7205     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7206         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7207         SendBoard(&first, currentMove+1);
7208         if(second.analyzing) {
7209             if(!second.useSetboard) SendToProgram("undo\n", &second);
7210             SendBoard(&second, currentMove+1);
7211         }
7212     } else {
7213         SendMoveToProgram(forwardMostMove-1, &first);
7214         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7215     }
7216     if (currentMove == cmailOldMove + 1) {
7217       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7218     }
7219   }
7220
7221   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7222
7223   switch (gameMode) {
7224   case EditGame:
7225     if(appData.testLegality)
7226     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7227     case MT_NONE:
7228     case MT_CHECK:
7229       break;
7230     case MT_CHECKMATE:
7231     case MT_STAINMATE:
7232       if (WhiteOnMove(currentMove)) {
7233         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7234       } else {
7235         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7236       }
7237       break;
7238     case MT_STALEMATE:
7239       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7240       break;
7241     }
7242     break;
7243
7244   case MachinePlaysBlack:
7245   case MachinePlaysWhite:
7246     /* disable certain menu options while machine is thinking */
7247     SetMachineThinkingEnables();
7248     break;
7249
7250   default:
7251     break;
7252   }
7253
7254   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7255   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7256
7257   if(bookHit) { // [HGM] book: simulate book reply
7258         static char bookMove[MSG_SIZ]; // a bit generous?
7259
7260         programStats.nodes = programStats.depth = programStats.time =
7261         programStats.score = programStats.got_only_move = 0;
7262         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7263
7264         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7265         strcat(bookMove, bookHit);
7266         HandleMachineMove(bookMove, &first);
7267   }
7268   return 1;
7269 }
7270
7271 void
7272 MarkByFEN(char *fen)
7273 {
7274         int r, f;
7275         if(!appData.markers || !appData.highlightDragging) return;
7276         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7277         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7278         while(*fen) {
7279             int s = 0;
7280             marker[r][f] = 0;
7281             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7282             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7283             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7284             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7285             if(*fen == 'T') marker[r][f++] = 0; else
7286             if(*fen == 'Y') marker[r][f++] = 1; else
7287             if(*fen == 'G') marker[r][f++] = 3; else
7288             if(*fen == 'B') marker[r][f++] = 4; else
7289             if(*fen == 'C') marker[r][f++] = 5; else
7290             if(*fen == 'M') marker[r][f++] = 6; else
7291             if(*fen == 'W') marker[r][f++] = 7; else
7292             if(*fen == 'D') marker[r][f++] = 8; else
7293             if(*fen == 'R') marker[r][f++] = 2; else {
7294                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7295               f += s; fen -= s>0;
7296             }
7297             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7298             if(r < 0) break;
7299             fen++;
7300         }
7301         DrawPosition(TRUE, NULL);
7302 }
7303
7304 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7305
7306 void
7307 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7308 {
7309     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7310     Markers *m = (Markers *) closure;
7311     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7312         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7313                          || kind == WhiteCapturesEnPassant
7314                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7315     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7316 }
7317
7318 static int hoverSavedValid;
7319
7320 void
7321 MarkTargetSquares (int clear)
7322 {
7323   int x, y, sum=0;
7324   if(clear) { // no reason to ever suppress clearing
7325     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7326     hoverSavedValid = 0;
7327     if(!sum) return; // nothing was cleared,no redraw needed
7328   } else {
7329     int capt = 0;
7330     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7331        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7332     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7333     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7334       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7335       if(capt)
7336       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7337     }
7338   }
7339   DrawPosition(FALSE, NULL);
7340 }
7341
7342 int
7343 Explode (Board board, int fromX, int fromY, int toX, int toY)
7344 {
7345     if(gameInfo.variant == VariantAtomic &&
7346        (board[toY][toX] != EmptySquare ||                     // capture?
7347         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7348                          board[fromY][fromX] == BlackPawn   )
7349       )) {
7350         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7351         return TRUE;
7352     }
7353     return FALSE;
7354 }
7355
7356 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7357
7358 int
7359 CanPromote (ChessSquare piece, int y)
7360 {
7361         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7362         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7363         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7364         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7365            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7366            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7367          gameInfo.variant == VariantMakruk) return FALSE;
7368         return (piece == BlackPawn && y <= zone ||
7369                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7370                 piece == BlackLance && y <= zone ||
7371                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7372 }
7373
7374 void
7375 HoverEvent (int xPix, int yPix, int x, int y)
7376 {
7377         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7378         int r, f;
7379         if(!first.highlight) return;
7380         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7381         if(x == oldX && y == oldY) return; // only do something if we enter new square
7382         oldFromX = fromX; oldFromY = fromY;
7383         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7384           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7385             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7386           hoverSavedValid = 1;
7387         } else if(oldX != x || oldY != y) {
7388           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7389           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7390           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7391             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7392           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7393             char buf[MSG_SIZ];
7394             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7395             SendToProgram(buf, &first);
7396           }
7397           oldX = x; oldY = y;
7398 //        SetHighlights(fromX, fromY, x, y);
7399         }
7400 }
7401
7402 void ReportClick(char *action, int x, int y)
7403 {
7404         char buf[MSG_SIZ]; // Inform engine of what user does
7405         int r, f;
7406         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7407           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7408             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7409         if(!first.highlight || gameMode == EditPosition) return;
7410         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7411         SendToProgram(buf, &first);
7412 }
7413
7414 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7415
7416 void
7417 LeftClick (ClickType clickType, int xPix, int yPix)
7418 {
7419     int x, y;
7420     Boolean saveAnimate;
7421     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7422     char promoChoice = NULLCHAR;
7423     ChessSquare piece;
7424     static TimeMark lastClickTime, prevClickTime;
7425
7426     x = EventToSquare(xPix, BOARD_WIDTH);
7427     y = EventToSquare(yPix, BOARD_HEIGHT);
7428     if (!flipView && y >= 0) {
7429         y = BOARD_HEIGHT - 1 - y;
7430     }
7431     if (flipView && x >= 0) {
7432         x = BOARD_WIDTH - 1 - x;
7433     }
7434
7435     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7436         static int dummy;
7437         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7438         right = TRUE;
7439         return;
7440     }
7441
7442     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7443
7444     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7445
7446     if (clickType == Press) ErrorPopDown();
7447     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7448
7449     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7450         defaultPromoChoice = promoSweep;
7451         promoSweep = EmptySquare;   // terminate sweep
7452         promoDefaultAltered = TRUE;
7453         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7454     }
7455
7456     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7457         if(clickType == Release) return; // ignore upclick of click-click destination
7458         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7459         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7460         if(gameInfo.holdingsWidth &&
7461                 (WhiteOnMove(currentMove)
7462                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7463                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7464             // click in right holdings, for determining promotion piece
7465             ChessSquare p = boards[currentMove][y][x];
7466             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7467             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7468             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7469                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7470                 fromX = fromY = -1;
7471                 return;
7472             }
7473         }
7474         DrawPosition(FALSE, boards[currentMove]);
7475         return;
7476     }
7477
7478     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7479     if(clickType == Press
7480             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7481               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7482               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7483         return;
7484
7485     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7486         // could be static click on premove from-square: abort premove
7487         gotPremove = 0;
7488         ClearPremoveHighlights();
7489     }
7490
7491     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7492         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7493
7494     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7495         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7496                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7497         defaultPromoChoice = DefaultPromoChoice(side);
7498     }
7499
7500     autoQueen = appData.alwaysPromoteToQueen;
7501
7502     if (fromX == -1) {
7503       int originalY = y;
7504       gatingPiece = EmptySquare;
7505       if (clickType != Press) {
7506         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7507             DragPieceEnd(xPix, yPix); dragging = 0;
7508             DrawPosition(FALSE, NULL);
7509         }
7510         return;
7511       }
7512       doubleClick = FALSE;
7513       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7514         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7515       }
7516       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7517       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7518          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7519          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7520             /* First square */
7521             if (OKToStartUserMove(fromX, fromY)) {
7522                 second = 0;
7523                 ReportClick("lift", x, y);
7524                 MarkTargetSquares(0);
7525                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7526                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7527                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7528                     promoSweep = defaultPromoChoice;
7529                     selectFlag = 0; lastX = xPix; lastY = yPix;
7530                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7531                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7532                 }
7533                 if (appData.highlightDragging) {
7534                     SetHighlights(fromX, fromY, -1, -1);
7535                 } else {
7536                     ClearHighlights();
7537                 }
7538             } else fromX = fromY = -1;
7539             return;
7540         }
7541     }
7542 printf("to click %d,%d\n",x,y);
7543     /* fromX != -1 */
7544     if (clickType == Press && gameMode != EditPosition) {
7545         ChessSquare fromP;
7546         ChessSquare toP;
7547         int frc;
7548
7549         // ignore off-board to clicks
7550         if(y < 0 || x < 0) return;
7551
7552         /* Check if clicking again on the same color piece */
7553         fromP = boards[currentMove][fromY][fromX];
7554         toP = boards[currentMove][y][x];
7555         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7556         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7557             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7558            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7559              WhitePawn <= toP && toP <= WhiteKing &&
7560              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7561              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7562             (BlackPawn <= fromP && fromP <= BlackKing &&
7563              BlackPawn <= toP && toP <= BlackKing &&
7564              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7565              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7566             /* Clicked again on same color piece -- changed his mind */
7567             second = (x == fromX && y == fromY);
7568             killX = killY = -1;
7569             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7570                 second = FALSE; // first double-click rather than scond click
7571                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7572             }
7573             promoDefaultAltered = FALSE;
7574             MarkTargetSquares(1);
7575            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7576             if (appData.highlightDragging) {
7577                 SetHighlights(x, y, -1, -1);
7578             } else {
7579                 ClearHighlights();
7580             }
7581             if (OKToStartUserMove(x, y)) {
7582                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7583                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7584                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7585                  gatingPiece = boards[currentMove][fromY][fromX];
7586                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7587                 fromX = x;
7588                 fromY = y; dragging = 1;
7589                 if(!second) ReportClick("lift", x, y);
7590                 MarkTargetSquares(0);
7591                 DragPieceBegin(xPix, yPix, FALSE);
7592                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7593                     promoSweep = defaultPromoChoice;
7594                     selectFlag = 0; lastX = xPix; lastY = yPix;
7595                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7596                 }
7597             }
7598            }
7599            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7600            second = FALSE;
7601         }
7602         // ignore clicks on holdings
7603         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7604     }
7605 printf("A type=%d\n",clickType);
7606
7607     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7608         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7609         return;
7610     }
7611
7612     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7613         DragPieceEnd(xPix, yPix); dragging = 0;
7614         if(clearFlag) {
7615             // a deferred attempt to click-click move an empty square on top of a piece
7616             boards[currentMove][y][x] = EmptySquare;
7617             ClearHighlights();
7618             DrawPosition(FALSE, boards[currentMove]);
7619             fromX = fromY = -1; clearFlag = 0;
7620             return;
7621         }
7622         if (appData.animateDragging) {
7623             /* Undo animation damage if any */
7624             DrawPosition(FALSE, NULL);
7625         }
7626         if (second) {
7627             /* Second up/down in same square; just abort move */
7628             second = 0;
7629             fromX = fromY = -1;
7630             gatingPiece = EmptySquare;
7631             MarkTargetSquares(1);
7632             ClearHighlights();
7633             gotPremove = 0;
7634             ClearPremoveHighlights();
7635         } else {
7636             /* First upclick in same square; start click-click mode */
7637             SetHighlights(x, y, -1, -1);
7638         }
7639         return;
7640     }
7641
7642     clearFlag = 0;
7643 printf("B\n");
7644     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7645        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7646         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7647         DisplayMessage(_("only marked squares are legal"),"");
7648         DrawPosition(TRUE, NULL);
7649         return; // ignore to-click
7650     }
7651 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7652     /* we now have a different from- and (possibly off-board) to-square */
7653     /* Completed move */
7654     if(!sweepSelecting) {
7655         toX = x;
7656         toY = y;
7657     }
7658
7659     piece = boards[currentMove][fromY][fromX];
7660
7661     saveAnimate = appData.animate;
7662     if (clickType == Press) {
7663         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7664         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7665             // must be Edit Position mode with empty-square selected
7666             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7667             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7668             return;
7669         }
7670         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7671             return;
7672         }
7673         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7674             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7675         } else
7676         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7677         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7678           if(appData.sweepSelect) {
7679             promoSweep = defaultPromoChoice;
7680             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7681             selectFlag = 0; lastX = xPix; lastY = yPix;
7682             Sweep(0); // Pawn that is going to promote: preview promotion piece
7683             sweepSelecting = 1;
7684             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7685             MarkTargetSquares(1);
7686           }
7687           return; // promo popup appears on up-click
7688         }
7689         /* Finish clickclick move */
7690         if (appData.animate || appData.highlightLastMove) {
7691             SetHighlights(fromX, fromY, toX, toY);
7692         } else {
7693             ClearHighlights();
7694         }
7695     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7696         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7697         if (appData.animate || appData.highlightLastMove) {
7698             SetHighlights(fromX, fromY, toX, toY);
7699         } else {
7700             ClearHighlights();
7701         }
7702     } else {
7703 #if 0
7704 // [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
7705         /* Finish drag move */
7706         if (appData.highlightLastMove) {
7707             SetHighlights(fromX, fromY, toX, toY);
7708         } else {
7709             ClearHighlights();
7710         }
7711 #endif
7712         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7713         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7714           dragging *= 2;            // flag button-less dragging if we are dragging
7715           MarkTargetSquares(1);
7716           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7717           else {
7718             kill2X = killX; kill2Y = killY;
7719             killX = x; killY = y;     //remeber this square as intermediate
7720             ReportClick("put", x, y); // and inform engine
7721             ReportClick("lift", x, y);
7722             MarkTargetSquares(0);
7723             return;
7724           }
7725         }
7726         DragPieceEnd(xPix, yPix); dragging = 0;
7727         /* Don't animate move and drag both */
7728         appData.animate = FALSE;
7729     }
7730
7731     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7732     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7733         ChessSquare piece = boards[currentMove][fromY][fromX];
7734         if(gameMode == EditPosition && piece != EmptySquare &&
7735            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7736             int n;
7737
7738             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7739                 n = PieceToNumber(piece - (int)BlackPawn);
7740                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7741                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7742                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7743             } else
7744             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7745                 n = PieceToNumber(piece);
7746                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7747                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7748                 boards[currentMove][n][BOARD_WIDTH-2]++;
7749             }
7750             boards[currentMove][fromY][fromX] = EmptySquare;
7751         }
7752         ClearHighlights();
7753         fromX = fromY = -1;
7754         MarkTargetSquares(1);
7755         DrawPosition(TRUE, boards[currentMove]);
7756         return;
7757     }
7758
7759     // off-board moves should not be highlighted
7760     if(x < 0 || y < 0) ClearHighlights();
7761     else ReportClick("put", x, y);
7762
7763     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7764
7765     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7766
7767     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7768         SetHighlights(fromX, fromY, toX, toY);
7769         MarkTargetSquares(1);
7770         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7771             // [HGM] super: promotion to captured piece selected from holdings
7772             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7773             promotionChoice = TRUE;
7774             // kludge follows to temporarily execute move on display, without promoting yet
7775             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7776             boards[currentMove][toY][toX] = p;
7777             DrawPosition(FALSE, boards[currentMove]);
7778             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7779             boards[currentMove][toY][toX] = q;
7780             DisplayMessage("Click in holdings to choose piece", "");
7781             return;
7782         }
7783         PromotionPopUp(promoChoice);
7784     } else {
7785         int oldMove = currentMove;
7786         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7787         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7788         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7789         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7790            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7791             DrawPosition(TRUE, boards[currentMove]);
7792         MarkTargetSquares(1);
7793         fromX = fromY = -1;
7794     }
7795     appData.animate = saveAnimate;
7796     if (appData.animate || appData.animateDragging) {
7797         /* Undo animation damage if needed */
7798         DrawPosition(FALSE, NULL);
7799     }
7800 }
7801
7802 int
7803 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7804 {   // front-end-free part taken out of PieceMenuPopup
7805     int whichMenu; int xSqr, ySqr;
7806
7807     if(seekGraphUp) { // [HGM] seekgraph
7808         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7809         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7810         return -2;
7811     }
7812
7813     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7814          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7815         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7816         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7817         if(action == Press)   {
7818             originalFlip = flipView;
7819             flipView = !flipView; // temporarily flip board to see game from partners perspective
7820             DrawPosition(TRUE, partnerBoard);
7821             DisplayMessage(partnerStatus, "");
7822             partnerUp = TRUE;
7823         } else if(action == Release) {
7824             flipView = originalFlip;
7825             DrawPosition(TRUE, boards[currentMove]);
7826             partnerUp = FALSE;
7827         }
7828         return -2;
7829     }
7830
7831     xSqr = EventToSquare(x, BOARD_WIDTH);
7832     ySqr = EventToSquare(y, BOARD_HEIGHT);
7833     if (action == Release) {
7834         if(pieceSweep != EmptySquare) {
7835             EditPositionMenuEvent(pieceSweep, toX, toY);
7836             pieceSweep = EmptySquare;
7837         } else UnLoadPV(); // [HGM] pv
7838     }
7839     if (action != Press) return -2; // return code to be ignored
7840     switch (gameMode) {
7841       case IcsExamining:
7842         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7843       case EditPosition:
7844         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7845         if (xSqr < 0 || ySqr < 0) return -1;
7846         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7847         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7848         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7849         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7850         NextPiece(0);
7851         return 2; // grab
7852       case IcsObserving:
7853         if(!appData.icsEngineAnalyze) return -1;
7854       case IcsPlayingWhite:
7855       case IcsPlayingBlack:
7856         if(!appData.zippyPlay) goto noZip;
7857       case AnalyzeMode:
7858       case AnalyzeFile:
7859       case MachinePlaysWhite:
7860       case MachinePlaysBlack:
7861       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7862         if (!appData.dropMenu) {
7863           LoadPV(x, y);
7864           return 2; // flag front-end to grab mouse events
7865         }
7866         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7867            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7868       case EditGame:
7869       noZip:
7870         if (xSqr < 0 || ySqr < 0) return -1;
7871         if (!appData.dropMenu || appData.testLegality &&
7872             gameInfo.variant != VariantBughouse &&
7873             gameInfo.variant != VariantCrazyhouse) return -1;
7874         whichMenu = 1; // drop menu
7875         break;
7876       default:
7877         return -1;
7878     }
7879
7880     if (((*fromX = xSqr) < 0) ||
7881         ((*fromY = ySqr) < 0)) {
7882         *fromX = *fromY = -1;
7883         return -1;
7884     }
7885     if (flipView)
7886       *fromX = BOARD_WIDTH - 1 - *fromX;
7887     else
7888       *fromY = BOARD_HEIGHT - 1 - *fromY;
7889
7890     return whichMenu;
7891 }
7892
7893 void
7894 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7895 {
7896 //    char * hint = lastHint;
7897     FrontEndProgramStats stats;
7898
7899     stats.which = cps == &first ? 0 : 1;
7900     stats.depth = cpstats->depth;
7901     stats.nodes = cpstats->nodes;
7902     stats.score = cpstats->score;
7903     stats.time = cpstats->time;
7904     stats.pv = cpstats->movelist;
7905     stats.hint = lastHint;
7906     stats.an_move_index = 0;
7907     stats.an_move_count = 0;
7908
7909     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7910         stats.hint = cpstats->move_name;
7911         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7912         stats.an_move_count = cpstats->nr_moves;
7913     }
7914
7915     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
7916
7917     SetProgramStats( &stats );
7918 }
7919
7920 void
7921 ClearEngineOutputPane (int which)
7922 {
7923     static FrontEndProgramStats dummyStats;
7924     dummyStats.which = which;
7925     dummyStats.pv = "#";
7926     SetProgramStats( &dummyStats );
7927 }
7928
7929 #define MAXPLAYERS 500
7930
7931 char *
7932 TourneyStandings (int display)
7933 {
7934     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7935     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7936     char result, *p, *names[MAXPLAYERS];
7937
7938     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7939         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7940     names[0] = p = strdup(appData.participants);
7941     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7942
7943     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7944
7945     while(result = appData.results[nr]) {
7946         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7947         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7948         wScore = bScore = 0;
7949         switch(result) {
7950           case '+': wScore = 2; break;
7951           case '-': bScore = 2; break;
7952           case '=': wScore = bScore = 1; break;
7953           case ' ':
7954           case '*': return strdup("busy"); // tourney not finished
7955         }
7956         score[w] += wScore;
7957         score[b] += bScore;
7958         games[w]++;
7959         games[b]++;
7960         nr++;
7961     }
7962     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7963     for(w=0; w<nPlayers; w++) {
7964         bScore = -1;
7965         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7966         ranking[w] = b; points[w] = bScore; score[b] = -2;
7967     }
7968     p = malloc(nPlayers*34+1);
7969     for(w=0; w<nPlayers && w<display; w++)
7970         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7971     free(names[0]);
7972     return p;
7973 }
7974
7975 void
7976 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7977 {       // count all piece types
7978         int p, f, r;
7979         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7980         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7981         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7982                 p = board[r][f];
7983                 pCnt[p]++;
7984                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7985                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7986                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7987                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7988                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7989                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7990         }
7991 }
7992
7993 int
7994 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7995 {
7996         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7997         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7998
7999         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8000         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8001         if(myPawns == 2 && nMine == 3) // KPP
8002             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8003         if(myPawns == 1 && nMine == 2) // KP
8004             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8005         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8006             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8007         if(myPawns) return FALSE;
8008         if(pCnt[WhiteRook+side])
8009             return pCnt[BlackRook-side] ||
8010                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8011                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8012                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8013         if(pCnt[WhiteCannon+side]) {
8014             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8015             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8016         }
8017         if(pCnt[WhiteKnight+side])
8018             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8019         return FALSE;
8020 }
8021
8022 int
8023 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8024 {
8025         VariantClass v = gameInfo.variant;
8026
8027         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8028         if(v == VariantShatranj) return TRUE; // always winnable through baring
8029         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8030         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8031
8032         if(v == VariantXiangqi) {
8033                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8034
8035                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8036                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8037                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8038                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8039                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8040                 if(stale) // we have at least one last-rank P plus perhaps C
8041                     return majors // KPKX
8042                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8043                 else // KCA*E*
8044                     return pCnt[WhiteFerz+side] // KCAK
8045                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8046                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8047                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8048
8049         } else if(v == VariantKnightmate) {
8050                 if(nMine == 1) return FALSE;
8051                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8052         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8053                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8054
8055                 if(nMine == 1) return FALSE; // bare King
8056                 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
8057                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8058                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8059                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8060                 if(pCnt[WhiteKnight+side])
8061                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8062                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8063                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8064                 if(nBishops)
8065                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8066                 if(pCnt[WhiteAlfil+side])
8067                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8068                 if(pCnt[WhiteWazir+side])
8069                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8070         }
8071
8072         return TRUE;
8073 }
8074
8075 int
8076 CompareWithRights (Board b1, Board b2)
8077 {
8078     int rights = 0;
8079     if(!CompareBoards(b1, b2)) return FALSE;
8080     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8081     /* compare castling rights */
8082     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8083            rights++; /* King lost rights, while rook still had them */
8084     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8085         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8086            rights++; /* but at least one rook lost them */
8087     }
8088     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8089            rights++;
8090     if( b1[CASTLING][5] != NoRights ) {
8091         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8092            rights++;
8093     }
8094     return rights == 0;
8095 }
8096
8097 int
8098 Adjudicate (ChessProgramState *cps)
8099 {       // [HGM] some adjudications useful with buggy engines
8100         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8101         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8102         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8103         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8104         int k, drop, count = 0; static int bare = 1;
8105         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8106         Boolean canAdjudicate = !appData.icsActive;
8107
8108         // most tests only when we understand the game, i.e. legality-checking on
8109             if( appData.testLegality )
8110             {   /* [HGM] Some more adjudications for obstinate engines */
8111                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8112                 static int moveCount = 6;
8113                 ChessMove result;
8114                 char *reason = NULL;
8115
8116                 /* Count what is on board. */
8117                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8118
8119                 /* Some material-based adjudications that have to be made before stalemate test */
8120                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8121                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8122                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8123                      if(canAdjudicate && appData.checkMates) {
8124                          if(engineOpponent)
8125                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8126                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8127                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8128                          return 1;
8129                      }
8130                 }
8131
8132                 /* Bare King in Shatranj (loses) or Losers (wins) */
8133                 if( nrW == 1 || nrB == 1) {
8134                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8135                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8136                      if(canAdjudicate && appData.checkMates) {
8137                          if(engineOpponent)
8138                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8139                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8140                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8141                          return 1;
8142                      }
8143                   } else
8144                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8145                   {    /* bare King */
8146                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8147                         if(canAdjudicate && appData.checkMates) {
8148                             /* but only adjudicate if adjudication enabled */
8149                             if(engineOpponent)
8150                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8151                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8152                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8153                             return 1;
8154                         }
8155                   }
8156                 } else bare = 1;
8157
8158
8159             // don't wait for engine to announce game end if we can judge ourselves
8160             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8161               case MT_CHECK:
8162                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8163                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8164                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8165                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8166                             checkCnt++;
8167                         if(checkCnt >= 2) {
8168                             reason = "Xboard adjudication: 3rd check";
8169                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8170                             break;
8171                         }
8172                     }
8173                 }
8174               case MT_NONE:
8175               default:
8176                 break;
8177               case MT_STEALMATE:
8178               case MT_STALEMATE:
8179               case MT_STAINMATE:
8180                 reason = "Xboard adjudication: Stalemate";
8181                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8182                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8183                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8184                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8185                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8186                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8187                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8188                                                                         EP_CHECKMATE : EP_WINS);
8189                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8190                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8191                 }
8192                 break;
8193               case MT_CHECKMATE:
8194                 reason = "Xboard adjudication: Checkmate";
8195                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8196                 if(gameInfo.variant == VariantShogi) {
8197                     if(forwardMostMove > backwardMostMove
8198                        && moveList[forwardMostMove-1][1] == '@'
8199                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8200                         reason = "XBoard adjudication: pawn-drop mate";
8201                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8202                     }
8203                 }
8204                 break;
8205             }
8206
8207                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8208                     case EP_STALEMATE:
8209                         result = GameIsDrawn; break;
8210                     case EP_CHECKMATE:
8211                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8212                     case EP_WINS:
8213                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8214                     default:
8215                         result = EndOfFile;
8216                 }
8217                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8218                     if(engineOpponent)
8219                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8220                     GameEnds( result, reason, GE_XBOARD );
8221                     return 1;
8222                 }
8223
8224                 /* Next absolutely insufficient mating material. */
8225                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8226                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8227                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8228
8229                      /* always flag draws, for judging claims */
8230                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8231
8232                      if(canAdjudicate && appData.materialDraws) {
8233                          /* but only adjudicate them if adjudication enabled */
8234                          if(engineOpponent) {
8235                            SendToProgram("force\n", engineOpponent); // suppress reply
8236                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8237                          }
8238                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8239                          return 1;
8240                      }
8241                 }
8242
8243                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8244                 if(gameInfo.variant == VariantXiangqi ?
8245                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8246                  : nrW + nrB == 4 &&
8247                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8248                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8249                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8250                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8251                    ) ) {
8252                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8253                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8254                           if(engineOpponent) {
8255                             SendToProgram("force\n", engineOpponent); // suppress reply
8256                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8257                           }
8258                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8259                           return 1;
8260                      }
8261                 } else moveCount = 6;
8262             }
8263
8264         // Repetition draws and 50-move rule can be applied independently of legality testing
8265
8266                 /* Check for rep-draws */
8267                 count = 0;
8268                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8269                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8270                 for(k = forwardMostMove-2;
8271                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8272                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8273                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8274                     k-=2)
8275                 {   int rights=0;
8276                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8277                         /* compare castling rights */
8278                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8279                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8280                                 rights++; /* King lost rights, while rook still had them */
8281                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8282                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8283                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8284                                    rights++; /* but at least one rook lost them */
8285                         }
8286                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8287                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8288                                 rights++;
8289                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8290                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8291                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8292                                    rights++;
8293                         }
8294                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8295                             && appData.drawRepeats > 1) {
8296                              /* adjudicate after user-specified nr of repeats */
8297                              int result = GameIsDrawn;
8298                              char *details = "XBoard adjudication: repetition draw";
8299                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8300                                 // [HGM] xiangqi: check for forbidden perpetuals
8301                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8302                                 for(m=forwardMostMove; m>k; m-=2) {
8303                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8304                                         ourPerpetual = 0; // the current mover did not always check
8305                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8306                                         hisPerpetual = 0; // the opponent did not always check
8307                                 }
8308                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8309                                                                         ourPerpetual, hisPerpetual);
8310                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8311                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8312                                     details = "Xboard adjudication: perpetual checking";
8313                                 } else
8314                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8315                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8316                                 } else
8317                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8318                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8319                                         result = BlackWins;
8320                                         details = "Xboard adjudication: repetition";
8321                                     }
8322                                 } else // it must be XQ
8323                                 // Now check for perpetual chases
8324                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8325                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8326                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8327                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8328                                         static char resdet[MSG_SIZ];
8329                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8330                                         details = resdet;
8331                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8332                                     } else
8333                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8334                                         break; // Abort repetition-checking loop.
8335                                 }
8336                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8337                              }
8338                              if(engineOpponent) {
8339                                SendToProgram("force\n", engineOpponent); // suppress reply
8340                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8341                              }
8342                              GameEnds( result, details, GE_XBOARD );
8343                              return 1;
8344                         }
8345                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8346                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8347                     }
8348                 }
8349
8350                 /* Now we test for 50-move draws. Determine ply count */
8351                 count = forwardMostMove;
8352                 /* look for last irreversble move */
8353                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8354                     count--;
8355                 /* if we hit starting position, add initial plies */
8356                 if( count == backwardMostMove )
8357                     count -= initialRulePlies;
8358                 count = forwardMostMove - count;
8359                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8360                         // adjust reversible move counter for checks in Xiangqi
8361                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8362                         if(i < backwardMostMove) i = backwardMostMove;
8363                         while(i <= forwardMostMove) {
8364                                 lastCheck = inCheck; // check evasion does not count
8365                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8366                                 if(inCheck || lastCheck) count--; // check does not count
8367                                 i++;
8368                         }
8369                 }
8370                 if( count >= 100)
8371                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8372                          /* this is used to judge if draw claims are legal */
8373                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8374                          if(engineOpponent) {
8375                            SendToProgram("force\n", engineOpponent); // suppress reply
8376                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8377                          }
8378                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8379                          return 1;
8380                 }
8381
8382                 /* if draw offer is pending, treat it as a draw claim
8383                  * when draw condition present, to allow engines a way to
8384                  * claim draws before making their move to avoid a race
8385                  * condition occurring after their move
8386                  */
8387                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8388                          char *p = NULL;
8389                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8390                              p = "Draw claim: 50-move rule";
8391                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8392                              p = "Draw claim: 3-fold repetition";
8393                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8394                              p = "Draw claim: insufficient mating material";
8395                          if( p != NULL && canAdjudicate) {
8396                              if(engineOpponent) {
8397                                SendToProgram("force\n", engineOpponent); // suppress reply
8398                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8399                              }
8400                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8401                              return 1;
8402                          }
8403                 }
8404
8405                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8406                     if(engineOpponent) {
8407                       SendToProgram("force\n", engineOpponent); // suppress reply
8408                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8409                     }
8410                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8411                     return 1;
8412                 }
8413         return 0;
8414 }
8415
8416 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8417 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8418 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8419
8420 static int
8421 BitbaseProbe ()
8422 {
8423     int pieces[10], squares[10], cnt=0, r, f, res;
8424     static int loaded;
8425     static PPROBE_EGBB probeBB;
8426     if(!appData.testLegality) return 10;
8427     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8428     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8429     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8430     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8431         ChessSquare piece = boards[forwardMostMove][r][f];
8432         int black = (piece >= BlackPawn);
8433         int type = piece - black*BlackPawn;
8434         if(piece == EmptySquare) continue;
8435         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8436         if(type == WhiteKing) type = WhiteQueen + 1;
8437         type = egbbCode[type];
8438         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8439         pieces[cnt] = type + black*6;
8440         if(++cnt > 5) return 11;
8441     }
8442     pieces[cnt] = squares[cnt] = 0;
8443     // probe EGBB
8444     if(loaded == 2) return 13; // loading failed before
8445     if(loaded == 0) {
8446         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8447         HMODULE lib;
8448         PLOAD_EGBB loadBB;
8449         loaded = 2; // prepare for failure
8450         if(!path) return 13; // no egbb installed
8451         strncpy(buf, path + 8, MSG_SIZ);
8452         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8453         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8454         lib = LoadLibrary(buf);
8455         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8456         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8457         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8458         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8459         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8460         loaded = 1; // success!
8461     }
8462     res = probeBB(forwardMostMove & 1, pieces, squares);
8463     return res > 0 ? 1 : res < 0 ? -1 : 0;
8464 }
8465
8466 char *
8467 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8468 {   // [HGM] book: this routine intercepts moves to simulate book replies
8469     char *bookHit = NULL;
8470
8471     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8472         char buf[MSG_SIZ];
8473         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8474         SendToProgram(buf, cps);
8475     }
8476     //first determine if the incoming move brings opponent into his book
8477     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8478         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8479     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8480     if(bookHit != NULL && !cps->bookSuspend) {
8481         // make sure opponent is not going to reply after receiving move to book position
8482         SendToProgram("force\n", cps);
8483         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8484     }
8485     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8486     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8487     // now arrange restart after book miss
8488     if(bookHit) {
8489         // after a book hit we never send 'go', and the code after the call to this routine
8490         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8491         char buf[MSG_SIZ], *move = bookHit;
8492         if(cps->useSAN) {
8493             int fromX, fromY, toX, toY;
8494             char promoChar;
8495             ChessMove moveType;
8496             move = buf + 30;
8497             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8498                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8499                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8500                                     PosFlags(forwardMostMove),
8501                                     fromY, fromX, toY, toX, promoChar, move);
8502             } else {
8503                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8504                 bookHit = NULL;
8505             }
8506         }
8507         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8508         SendToProgram(buf, cps);
8509         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8510     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8511         SendToProgram("go\n", cps);
8512         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8513     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8514         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8515             SendToProgram("go\n", cps);
8516         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8517     }
8518     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8519 }
8520
8521 int
8522 LoadError (char *errmess, ChessProgramState *cps)
8523 {   // unloads engine and switches back to -ncp mode if it was first
8524     if(cps->initDone) return FALSE;
8525     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8526     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8527     cps->pr = NoProc;
8528     if(cps == &first) {
8529         appData.noChessProgram = TRUE;
8530         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8531         gameMode = BeginningOfGame; ModeHighlight();
8532         SetNCPMode();
8533     }
8534     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8535     DisplayMessage("", ""); // erase waiting message
8536     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8537     return TRUE;
8538 }
8539
8540 char *savedMessage;
8541 ChessProgramState *savedState;
8542 void
8543 DeferredBookMove (void)
8544 {
8545         if(savedState->lastPing != savedState->lastPong)
8546                     ScheduleDelayedEvent(DeferredBookMove, 10);
8547         else
8548         HandleMachineMove(savedMessage, savedState);
8549 }
8550
8551 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8552 static ChessProgramState *stalledEngine;
8553 static char stashedInputMove[MSG_SIZ];
8554
8555 void
8556 HandleMachineMove (char *message, ChessProgramState *cps)
8557 {
8558     static char firstLeg[20];
8559     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8560     char realname[MSG_SIZ];
8561     int fromX, fromY, toX, toY;
8562     ChessMove moveType;
8563     char promoChar, roar;
8564     char *p, *pv=buf1;
8565     int machineWhite, oldError;
8566     char *bookHit;
8567
8568     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8569         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8570         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8571             DisplayError(_("Invalid pairing from pairing engine"), 0);
8572             return;
8573         }
8574         pairingReceived = 1;
8575         NextMatchGame();
8576         return; // Skim the pairing messages here.
8577     }
8578
8579     oldError = cps->userError; cps->userError = 0;
8580
8581 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8582     /*
8583      * Kludge to ignore BEL characters
8584      */
8585     while (*message == '\007') message++;
8586
8587     /*
8588      * [HGM] engine debug message: ignore lines starting with '#' character
8589      */
8590     if(cps->debug && *message == '#') return;
8591
8592     /*
8593      * Look for book output
8594      */
8595     if (cps == &first && bookRequested) {
8596         if (message[0] == '\t' || message[0] == ' ') {
8597             /* Part of the book output is here; append it */
8598             strcat(bookOutput, message);
8599             strcat(bookOutput, "  \n");
8600             return;
8601         } else if (bookOutput[0] != NULLCHAR) {
8602             /* All of book output has arrived; display it */
8603             char *p = bookOutput;
8604             while (*p != NULLCHAR) {
8605                 if (*p == '\t') *p = ' ';
8606                 p++;
8607             }
8608             DisplayInformation(bookOutput);
8609             bookRequested = FALSE;
8610             /* Fall through to parse the current output */
8611         }
8612     }
8613
8614     /*
8615      * Look for machine move.
8616      */
8617     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8618         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8619     {
8620         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8621             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8622             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8623             stalledEngine = cps;
8624             if(appData.ponderNextMove) { // bring opponent out of ponder
8625                 if(gameMode == TwoMachinesPlay) {
8626                     if(cps->other->pause)
8627                         PauseEngine(cps->other);
8628                     else
8629                         SendToProgram("easy\n", cps->other);
8630                 }
8631             }
8632             StopClocks();
8633             return;
8634         }
8635
8636         /* This method is only useful on engines that support ping */
8637         if (cps->lastPing != cps->lastPong) {
8638           if (gameMode == BeginningOfGame) {
8639             /* Extra move from before last new; ignore */
8640             if (appData.debugMode) {
8641                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8642             }
8643           } else {
8644             if (appData.debugMode) {
8645                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8646                         cps->which, gameMode);
8647             }
8648
8649             SendToProgram("undo\n", cps);
8650           }
8651           return;
8652         }
8653
8654         switch (gameMode) {
8655           case BeginningOfGame:
8656             /* Extra move from before last reset; ignore */
8657             if (appData.debugMode) {
8658                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8659             }
8660             return;
8661
8662           case EndOfGame:
8663           case IcsIdle:
8664           default:
8665             /* Extra move after we tried to stop.  The mode test is
8666                not a reliable way of detecting this problem, but it's
8667                the best we can do on engines that don't support ping.
8668             */
8669             if (appData.debugMode) {
8670                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8671                         cps->which, gameMode);
8672             }
8673             SendToProgram("undo\n", cps);
8674             return;
8675
8676           case MachinePlaysWhite:
8677           case IcsPlayingWhite:
8678             machineWhite = TRUE;
8679             break;
8680
8681           case MachinePlaysBlack:
8682           case IcsPlayingBlack:
8683             machineWhite = FALSE;
8684             break;
8685
8686           case TwoMachinesPlay:
8687             machineWhite = (cps->twoMachinesColor[0] == 'w');
8688             break;
8689         }
8690         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8691             if (appData.debugMode) {
8692                 fprintf(debugFP,
8693                         "Ignoring move out of turn by %s, gameMode %d"
8694                         ", forwardMost %d\n",
8695                         cps->which, gameMode, forwardMostMove);
8696             }
8697             return;
8698         }
8699
8700         if(cps->alphaRank) AlphaRank(machineMove, 4);
8701
8702         // [HGM] lion: (some very limited) support for Alien protocol
8703         killX = killY = kill2X = kill2Y = -1;
8704         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8705             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8706             return;
8707         }
8708         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8709             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8710             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8711         }
8712         if(firstLeg[0]) { // there was a previous leg;
8713             // only support case where same piece makes two step
8714             char buf[20], *p = machineMove+1, *q = buf+1, f;
8715             safeStrCpy(buf, machineMove, 20);
8716             while(isdigit(*q)) q++; // find start of to-square
8717             safeStrCpy(machineMove, firstLeg, 20);
8718             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8719             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8720             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8721             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8722             firstLeg[0] = NULLCHAR;
8723         }
8724
8725         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8726                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8727             /* Machine move could not be parsed; ignore it. */
8728           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8729                     machineMove, _(cps->which));
8730             DisplayMoveError(buf1);
8731             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8732                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8733             if (gameMode == TwoMachinesPlay) {
8734               GameEnds(machineWhite ? BlackWins : WhiteWins,
8735                        buf1, GE_XBOARD);
8736             }
8737             return;
8738         }
8739
8740         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8741         /* So we have to redo legality test with true e.p. status here,  */
8742         /* to make sure an illegal e.p. capture does not slip through,   */
8743         /* to cause a forfeit on a justified illegal-move complaint      */
8744         /* of the opponent.                                              */
8745         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8746            ChessMove moveType;
8747            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8748                              fromY, fromX, toY, toX, promoChar);
8749             if(moveType == IllegalMove) {
8750               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8751                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8752                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8753                            buf1, GE_XBOARD);
8754                 return;
8755            } else if(!appData.fischerCastling)
8756            /* [HGM] Kludge to handle engines that send FRC-style castling
8757               when they shouldn't (like TSCP-Gothic) */
8758            switch(moveType) {
8759              case WhiteASideCastleFR:
8760              case BlackASideCastleFR:
8761                toX+=2;
8762                currentMoveString[2]++;
8763                break;
8764              case WhiteHSideCastleFR:
8765              case BlackHSideCastleFR:
8766                toX--;
8767                currentMoveString[2]--;
8768                break;
8769              default: ; // nothing to do, but suppresses warning of pedantic compilers
8770            }
8771         }
8772         hintRequested = FALSE;
8773         lastHint[0] = NULLCHAR;
8774         bookRequested = FALSE;
8775         /* Program may be pondering now */
8776         cps->maybeThinking = TRUE;
8777         if (cps->sendTime == 2) cps->sendTime = 1;
8778         if (cps->offeredDraw) cps->offeredDraw--;
8779
8780         /* [AS] Save move info*/
8781         pvInfoList[ forwardMostMove ].score = programStats.score;
8782         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8783         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8784
8785         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8786
8787         /* Test suites abort the 'game' after one move */
8788         if(*appData.finger) {
8789            static FILE *f;
8790            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8791            if(!f) f = fopen(appData.finger, "w");
8792            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8793            else { DisplayFatalError("Bad output file", errno, 0); return; }
8794            free(fen);
8795            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8796         }
8797
8798         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8799         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8800             int count = 0;
8801
8802             while( count < adjudicateLossPlies ) {
8803                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8804
8805                 if( count & 1 ) {
8806                     score = -score; /* Flip score for winning side */
8807                 }
8808
8809                 if( score > appData.adjudicateLossThreshold ) {
8810                     break;
8811                 }
8812
8813                 count++;
8814             }
8815
8816             if( count >= adjudicateLossPlies ) {
8817                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8818
8819                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8820                     "Xboard adjudication",
8821                     GE_XBOARD );
8822
8823                 return;
8824             }
8825         }
8826
8827         if(Adjudicate(cps)) {
8828             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8829             return; // [HGM] adjudicate: for all automatic game ends
8830         }
8831
8832 #if ZIPPY
8833         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8834             first.initDone) {
8835           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8836                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8837                 SendToICS("draw ");
8838                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8839           }
8840           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8841           ics_user_moved = 1;
8842           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8843                 char buf[3*MSG_SIZ];
8844
8845                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8846                         programStats.score / 100.,
8847                         programStats.depth,
8848                         programStats.time / 100.,
8849                         (unsigned int)programStats.nodes,
8850                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8851                         programStats.movelist);
8852                 SendToICS(buf);
8853           }
8854         }
8855 #endif
8856
8857         /* [AS] Clear stats for next move */
8858         ClearProgramStats();
8859         thinkOutput[0] = NULLCHAR;
8860         hiddenThinkOutputState = 0;
8861
8862         bookHit = NULL;
8863         if (gameMode == TwoMachinesPlay) {
8864             /* [HGM] relaying draw offers moved to after reception of move */
8865             /* and interpreting offer as claim if it brings draw condition */
8866             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8867                 SendToProgram("draw\n", cps->other);
8868             }
8869             if (cps->other->sendTime) {
8870                 SendTimeRemaining(cps->other,
8871                                   cps->other->twoMachinesColor[0] == 'w');
8872             }
8873             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8874             if (firstMove && !bookHit) {
8875                 firstMove = FALSE;
8876                 if (cps->other->useColors) {
8877                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8878                 }
8879                 SendToProgram("go\n", cps->other);
8880             }
8881             cps->other->maybeThinking = TRUE;
8882         }
8883
8884         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8885
8886         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8887
8888         if (!pausing && appData.ringBellAfterMoves) {
8889             if(!roar) RingBell();
8890         }
8891
8892         /*
8893          * Reenable menu items that were disabled while
8894          * machine was thinking
8895          */
8896         if (gameMode != TwoMachinesPlay)
8897             SetUserThinkingEnables();
8898
8899         // [HGM] book: after book hit opponent has received move and is now in force mode
8900         // force the book reply into it, and then fake that it outputted this move by jumping
8901         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8902         if(bookHit) {
8903                 static char bookMove[MSG_SIZ]; // a bit generous?
8904
8905                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8906                 strcat(bookMove, bookHit);
8907                 message = bookMove;
8908                 cps = cps->other;
8909                 programStats.nodes = programStats.depth = programStats.time =
8910                 programStats.score = programStats.got_only_move = 0;
8911                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8912
8913                 if(cps->lastPing != cps->lastPong) {
8914                     savedMessage = message; // args for deferred call
8915                     savedState = cps;
8916                     ScheduleDelayedEvent(DeferredBookMove, 10);
8917                     return;
8918                 }
8919                 goto FakeBookMove;
8920         }
8921
8922         return;
8923     }
8924
8925     /* Set special modes for chess engines.  Later something general
8926      *  could be added here; for now there is just one kludge feature,
8927      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8928      *  when "xboard" is given as an interactive command.
8929      */
8930     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8931         cps->useSigint = FALSE;
8932         cps->useSigterm = FALSE;
8933     }
8934     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8935       ParseFeatures(message+8, cps);
8936       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8937     }
8938
8939     if (!strncmp(message, "setup ", 6) && 
8940         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8941           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8942                                         ) { // [HGM] allow first engine to define opening position
8943       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8944       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8945       *buf = NULLCHAR;
8946       if(sscanf(message, "setup (%s", buf) == 1) {
8947         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8948         ASSIGN(appData.pieceToCharTable, buf);
8949       }
8950       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8951       if(dummy >= 3) {
8952         while(message[s] && message[s++] != ' ');
8953         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8954            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8955             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8956             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8957           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8958           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8959           startedFromSetupPosition = FALSE;
8960         }
8961       }
8962       if(startedFromSetupPosition) return;
8963       ParseFEN(boards[0], &dummy, message+s, FALSE);
8964       DrawPosition(TRUE, boards[0]);
8965       CopyBoard(initialPosition, boards[0]);
8966       startedFromSetupPosition = TRUE;
8967       return;
8968     }
8969     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8970       ChessSquare piece = WhitePawn;
8971       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8972       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8973       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8974       piece += CharToPiece(ID) - WhitePawn;
8975       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8976       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8977       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8978       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8979       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8980       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8981                                                && gameInfo.variant != VariantGreat
8982                                                && gameInfo.variant != VariantFairy    ) return;
8983       if(piece < EmptySquare) {
8984         pieceDefs = TRUE;
8985         ASSIGN(pieceDesc[piece], buf1);
8986         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8987       }
8988       return;
8989     }
8990     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8991      * want this, I was asked to put it in, and obliged.
8992      */
8993     if (!strncmp(message, "setboard ", 9)) {
8994         Board initial_position;
8995
8996         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8997
8998         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8999             DisplayError(_("Bad FEN received from engine"), 0);
9000             return ;
9001         } else {
9002            Reset(TRUE, FALSE);
9003            CopyBoard(boards[0], initial_position);
9004            initialRulePlies = FENrulePlies;
9005            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9006            else gameMode = MachinePlaysBlack;
9007            DrawPosition(FALSE, boards[currentMove]);
9008         }
9009         return;
9010     }
9011
9012     /*
9013      * Look for communication commands
9014      */
9015     if (!strncmp(message, "telluser ", 9)) {
9016         if(message[9] == '\\' && message[10] == '\\')
9017             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9018         PlayTellSound();
9019         DisplayNote(message + 9);
9020         return;
9021     }
9022     if (!strncmp(message, "tellusererror ", 14)) {
9023         cps->userError = 1;
9024         if(message[14] == '\\' && message[15] == '\\')
9025             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9026         PlayTellSound();
9027         DisplayError(message + 14, 0);
9028         return;
9029     }
9030     if (!strncmp(message, "tellopponent ", 13)) {
9031       if (appData.icsActive) {
9032         if (loggedOn) {
9033           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9034           SendToICS(buf1);
9035         }
9036       } else {
9037         DisplayNote(message + 13);
9038       }
9039       return;
9040     }
9041     if (!strncmp(message, "tellothers ", 11)) {
9042       if (appData.icsActive) {
9043         if (loggedOn) {
9044           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9045           SendToICS(buf1);
9046         }
9047       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9048       return;
9049     }
9050     if (!strncmp(message, "tellall ", 8)) {
9051       if (appData.icsActive) {
9052         if (loggedOn) {
9053           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9054           SendToICS(buf1);
9055         }
9056       } else {
9057         DisplayNote(message + 8);
9058       }
9059       return;
9060     }
9061     if (strncmp(message, "warning", 7) == 0) {
9062         /* Undocumented feature, use tellusererror in new code */
9063         DisplayError(message, 0);
9064         return;
9065     }
9066     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9067         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9068         strcat(realname, " query");
9069         AskQuestion(realname, buf2, buf1, cps->pr);
9070         return;
9071     }
9072     /* Commands from the engine directly to ICS.  We don't allow these to be
9073      *  sent until we are logged on. Crafty kibitzes have been known to
9074      *  interfere with the login process.
9075      */
9076     if (loggedOn) {
9077         if (!strncmp(message, "tellics ", 8)) {
9078             SendToICS(message + 8);
9079             SendToICS("\n");
9080             return;
9081         }
9082         if (!strncmp(message, "tellicsnoalias ", 15)) {
9083             SendToICS(ics_prefix);
9084             SendToICS(message + 15);
9085             SendToICS("\n");
9086             return;
9087         }
9088         /* The following are for backward compatibility only */
9089         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9090             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9091             SendToICS(ics_prefix);
9092             SendToICS(message);
9093             SendToICS("\n");
9094             return;
9095         }
9096     }
9097     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9098         if(initPing == cps->lastPong) {
9099             if(gameInfo.variant == VariantUnknown) {
9100                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9101                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9102                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9103             }
9104             initPing = -1;
9105         }
9106         return;
9107     }
9108     if(!strncmp(message, "highlight ", 10)) {
9109         if(appData.testLegality && !*engineVariant && appData.markers) return;
9110         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9111         return;
9112     }
9113     if(!strncmp(message, "click ", 6)) {
9114         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9115         if(appData.testLegality || !appData.oneClick) return;
9116         sscanf(message+6, "%c%d%c", &f, &y, &c);
9117         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9118         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9119         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9120         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9121         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9122         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9123             LeftClick(Release, lastLeftX, lastLeftY);
9124         controlKey  = (c == ',');
9125         LeftClick(Press, x, y);
9126         LeftClick(Release, x, y);
9127         first.highlight = f;
9128         return;
9129     }
9130     /*
9131      * If the move is illegal, cancel it and redraw the board.
9132      * Also deal with other error cases.  Matching is rather loose
9133      * here to accommodate engines written before the spec.
9134      */
9135     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9136         strncmp(message, "Error", 5) == 0) {
9137         if (StrStr(message, "name") ||
9138             StrStr(message, "rating") || StrStr(message, "?") ||
9139             StrStr(message, "result") || StrStr(message, "board") ||
9140             StrStr(message, "bk") || StrStr(message, "computer") ||
9141             StrStr(message, "variant") || StrStr(message, "hint") ||
9142             StrStr(message, "random") || StrStr(message, "depth") ||
9143             StrStr(message, "accepted")) {
9144             return;
9145         }
9146         if (StrStr(message, "protover")) {
9147           /* Program is responding to input, so it's apparently done
9148              initializing, and this error message indicates it is
9149              protocol version 1.  So we don't need to wait any longer
9150              for it to initialize and send feature commands. */
9151           FeatureDone(cps, 1);
9152           cps->protocolVersion = 1;
9153           return;
9154         }
9155         cps->maybeThinking = FALSE;
9156
9157         if (StrStr(message, "draw")) {
9158             /* Program doesn't have "draw" command */
9159             cps->sendDrawOffers = 0;
9160             return;
9161         }
9162         if (cps->sendTime != 1 &&
9163             (StrStr(message, "time") || StrStr(message, "otim"))) {
9164           /* Program apparently doesn't have "time" or "otim" command */
9165           cps->sendTime = 0;
9166           return;
9167         }
9168         if (StrStr(message, "analyze")) {
9169             cps->analysisSupport = FALSE;
9170             cps->analyzing = FALSE;
9171 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9172             EditGameEvent(); // [HGM] try to preserve loaded game
9173             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9174             DisplayError(buf2, 0);
9175             return;
9176         }
9177         if (StrStr(message, "(no matching move)st")) {
9178           /* Special kludge for GNU Chess 4 only */
9179           cps->stKludge = TRUE;
9180           SendTimeControl(cps, movesPerSession, timeControl,
9181                           timeIncrement, appData.searchDepth,
9182                           searchTime);
9183           return;
9184         }
9185         if (StrStr(message, "(no matching move)sd")) {
9186           /* Special kludge for GNU Chess 4 only */
9187           cps->sdKludge = TRUE;
9188           SendTimeControl(cps, movesPerSession, timeControl,
9189                           timeIncrement, appData.searchDepth,
9190                           searchTime);
9191           return;
9192         }
9193         if (!StrStr(message, "llegal")) {
9194             return;
9195         }
9196         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9197             gameMode == IcsIdle) return;
9198         if (forwardMostMove <= backwardMostMove) return;
9199         if (pausing) PauseEvent();
9200       if(appData.forceIllegal) {
9201             // [HGM] illegal: machine refused move; force position after move into it
9202           SendToProgram("force\n", cps);
9203           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9204                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9205                 // when black is to move, while there might be nothing on a2 or black
9206                 // might already have the move. So send the board as if white has the move.
9207                 // But first we must change the stm of the engine, as it refused the last move
9208                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9209                 if(WhiteOnMove(forwardMostMove)) {
9210                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9211                     SendBoard(cps, forwardMostMove); // kludgeless board
9212                 } else {
9213                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9214                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9215                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9216                 }
9217           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9218             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9219                  gameMode == TwoMachinesPlay)
9220               SendToProgram("go\n", cps);
9221             return;
9222       } else
9223         if (gameMode == PlayFromGameFile) {
9224             /* Stop reading this game file */
9225             gameMode = EditGame;
9226             ModeHighlight();
9227         }
9228         /* [HGM] illegal-move claim should forfeit game when Xboard */
9229         /* only passes fully legal moves                            */
9230         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9231             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9232                                 "False illegal-move claim", GE_XBOARD );
9233             return; // do not take back move we tested as valid
9234         }
9235         currentMove = forwardMostMove-1;
9236         DisplayMove(currentMove-1); /* before DisplayMoveError */
9237         SwitchClocks(forwardMostMove-1); // [HGM] race
9238         DisplayBothClocks();
9239         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9240                 parseList[currentMove], _(cps->which));
9241         DisplayMoveError(buf1);
9242         DrawPosition(FALSE, boards[currentMove]);
9243
9244         SetUserThinkingEnables();
9245         return;
9246     }
9247     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9248         /* Program has a broken "time" command that
9249            outputs a string not ending in newline.
9250            Don't use it. */
9251         cps->sendTime = 0;
9252     }
9253     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9254         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9255             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9256     }
9257
9258     /*
9259      * If chess program startup fails, exit with an error message.
9260      * Attempts to recover here are futile. [HGM] Well, we try anyway
9261      */
9262     if ((StrStr(message, "unknown host") != NULL)
9263         || (StrStr(message, "No remote directory") != NULL)
9264         || (StrStr(message, "not found") != NULL)
9265         || (StrStr(message, "No such file") != NULL)
9266         || (StrStr(message, "can't alloc") != NULL)
9267         || (StrStr(message, "Permission denied") != NULL)) {
9268
9269         cps->maybeThinking = FALSE;
9270         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9271                 _(cps->which), cps->program, cps->host, message);
9272         RemoveInputSource(cps->isr);
9273         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9274             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9275             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9276         }
9277         return;
9278     }
9279
9280     /*
9281      * Look for hint output
9282      */
9283     if (sscanf(message, "Hint: %s", buf1) == 1) {
9284         if (cps == &first && hintRequested) {
9285             hintRequested = FALSE;
9286             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9287                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9288                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9289                                     PosFlags(forwardMostMove),
9290                                     fromY, fromX, toY, toX, promoChar, buf1);
9291                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9292                 DisplayInformation(buf2);
9293             } else {
9294                 /* Hint move could not be parsed!? */
9295               snprintf(buf2, sizeof(buf2),
9296                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9297                         buf1, _(cps->which));
9298                 DisplayError(buf2, 0);
9299             }
9300         } else {
9301           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9302         }
9303         return;
9304     }
9305
9306     /*
9307      * Ignore other messages if game is not in progress
9308      */
9309     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9310         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9311
9312     /*
9313      * look for win, lose, draw, or draw offer
9314      */
9315     if (strncmp(message, "1-0", 3) == 0) {
9316         char *p, *q, *r = "";
9317         p = strchr(message, '{');
9318         if (p) {
9319             q = strchr(p, '}');
9320             if (q) {
9321                 *q = NULLCHAR;
9322                 r = p + 1;
9323             }
9324         }
9325         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9326         return;
9327     } else if (strncmp(message, "0-1", 3) == 0) {
9328         char *p, *q, *r = "";
9329         p = strchr(message, '{');
9330         if (p) {
9331             q = strchr(p, '}');
9332             if (q) {
9333                 *q = NULLCHAR;
9334                 r = p + 1;
9335             }
9336         }
9337         /* Kludge for Arasan 4.1 bug */
9338         if (strcmp(r, "Black resigns") == 0) {
9339             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9340             return;
9341         }
9342         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9343         return;
9344     } else if (strncmp(message, "1/2", 3) == 0) {
9345         char *p, *q, *r = "";
9346         p = strchr(message, '{');
9347         if (p) {
9348             q = strchr(p, '}');
9349             if (q) {
9350                 *q = NULLCHAR;
9351                 r = p + 1;
9352             }
9353         }
9354
9355         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9356         return;
9357
9358     } else if (strncmp(message, "White resign", 12) == 0) {
9359         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9360         return;
9361     } else if (strncmp(message, "Black resign", 12) == 0) {
9362         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9363         return;
9364     } else if (strncmp(message, "White matches", 13) == 0 ||
9365                strncmp(message, "Black matches", 13) == 0   ) {
9366         /* [HGM] ignore GNUShogi noises */
9367         return;
9368     } else if (strncmp(message, "White", 5) == 0 &&
9369                message[5] != '(' &&
9370                StrStr(message, "Black") == NULL) {
9371         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9372         return;
9373     } else if (strncmp(message, "Black", 5) == 0 &&
9374                message[5] != '(') {
9375         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9376         return;
9377     } else if (strcmp(message, "resign") == 0 ||
9378                strcmp(message, "computer resigns") == 0) {
9379         switch (gameMode) {
9380           case MachinePlaysBlack:
9381           case IcsPlayingBlack:
9382             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9383             break;
9384           case MachinePlaysWhite:
9385           case IcsPlayingWhite:
9386             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9387             break;
9388           case TwoMachinesPlay:
9389             if (cps->twoMachinesColor[0] == 'w')
9390               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9391             else
9392               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9393             break;
9394           default:
9395             /* can't happen */
9396             break;
9397         }
9398         return;
9399     } else if (strncmp(message, "opponent mates", 14) == 0) {
9400         switch (gameMode) {
9401           case MachinePlaysBlack:
9402           case IcsPlayingBlack:
9403             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9404             break;
9405           case MachinePlaysWhite:
9406           case IcsPlayingWhite:
9407             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9408             break;
9409           case TwoMachinesPlay:
9410             if (cps->twoMachinesColor[0] == 'w')
9411               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9412             else
9413               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9414             break;
9415           default:
9416             /* can't happen */
9417             break;
9418         }
9419         return;
9420     } else if (strncmp(message, "computer mates", 14) == 0) {
9421         switch (gameMode) {
9422           case MachinePlaysBlack:
9423           case IcsPlayingBlack:
9424             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9425             break;
9426           case MachinePlaysWhite:
9427           case IcsPlayingWhite:
9428             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9429             break;
9430           case TwoMachinesPlay:
9431             if (cps->twoMachinesColor[0] == 'w')
9432               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9433             else
9434               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9435             break;
9436           default:
9437             /* can't happen */
9438             break;
9439         }
9440         return;
9441     } else if (strncmp(message, "checkmate", 9) == 0) {
9442         if (WhiteOnMove(forwardMostMove)) {
9443             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9444         } else {
9445             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9446         }
9447         return;
9448     } else if (strstr(message, "Draw") != NULL ||
9449                strstr(message, "game is a draw") != NULL) {
9450         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9451         return;
9452     } else if (strstr(message, "offer") != NULL &&
9453                strstr(message, "draw") != NULL) {
9454 #if ZIPPY
9455         if (appData.zippyPlay && first.initDone) {
9456             /* Relay offer to ICS */
9457             SendToICS(ics_prefix);
9458             SendToICS("draw\n");
9459         }
9460 #endif
9461         cps->offeredDraw = 2; /* valid until this engine moves twice */
9462         if (gameMode == TwoMachinesPlay) {
9463             if (cps->other->offeredDraw) {
9464                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9465             /* [HGM] in two-machine mode we delay relaying draw offer      */
9466             /* until after we also have move, to see if it is really claim */
9467             }
9468         } else if (gameMode == MachinePlaysWhite ||
9469                    gameMode == MachinePlaysBlack) {
9470           if (userOfferedDraw) {
9471             DisplayInformation(_("Machine accepts your draw offer"));
9472             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9473           } else {
9474             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9475           }
9476         }
9477     }
9478
9479
9480     /*
9481      * Look for thinking output
9482      */
9483     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9484           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9485                                 ) {
9486         int plylev, mvleft, mvtot, curscore, time;
9487         char mvname[MOVE_LEN];
9488         u64 nodes; // [DM]
9489         char plyext;
9490         int ignore = FALSE;
9491         int prefixHint = FALSE;
9492         mvname[0] = NULLCHAR;
9493
9494         switch (gameMode) {
9495           case MachinePlaysBlack:
9496           case IcsPlayingBlack:
9497             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9498             break;
9499           case MachinePlaysWhite:
9500           case IcsPlayingWhite:
9501             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9502             break;
9503           case AnalyzeMode:
9504           case AnalyzeFile:
9505             break;
9506           case IcsObserving: /* [DM] icsEngineAnalyze */
9507             if (!appData.icsEngineAnalyze) ignore = TRUE;
9508             break;
9509           case TwoMachinesPlay:
9510             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9511                 ignore = TRUE;
9512             }
9513             break;
9514           default:
9515             ignore = TRUE;
9516             break;
9517         }
9518
9519         if (!ignore) {
9520             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9521             buf1[0] = NULLCHAR;
9522             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9523                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9524
9525                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9526                     nodes += u64Const(0x100000000);
9527
9528                 if (plyext != ' ' && plyext != '\t') {
9529                     time *= 100;
9530                 }
9531
9532                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9533                 if( cps->scoreIsAbsolute &&
9534                     ( gameMode == MachinePlaysBlack ||
9535                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9536                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9537                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9538                      !WhiteOnMove(currentMove)
9539                     ) )
9540                 {
9541                     curscore = -curscore;
9542                 }
9543
9544                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9545
9546                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9547                         char buf[MSG_SIZ];
9548                         FILE *f;
9549                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9550                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9551                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9552                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9553                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9554                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9555                                 fclose(f);
9556                         }
9557                         else
9558                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9559                           DisplayError(_("failed writing PV"), 0);
9560                 }
9561
9562                 tempStats.depth = plylev;
9563                 tempStats.nodes = nodes;
9564                 tempStats.time = time;
9565                 tempStats.score = curscore;
9566                 tempStats.got_only_move = 0;
9567
9568                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9569                         int ticklen;
9570
9571                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9572                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9573                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9574                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9575                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9576                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9577                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9578                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9579                 }
9580
9581                 /* Buffer overflow protection */
9582                 if (pv[0] != NULLCHAR) {
9583                     if (strlen(pv) >= sizeof(tempStats.movelist)
9584                         && appData.debugMode) {
9585                         fprintf(debugFP,
9586                                 "PV is too long; using the first %u bytes.\n",
9587                                 (unsigned) sizeof(tempStats.movelist) - 1);
9588                     }
9589
9590                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9591                 } else {
9592                     sprintf(tempStats.movelist, " no PV\n");
9593                 }
9594
9595                 if (tempStats.seen_stat) {
9596                     tempStats.ok_to_send = 1;
9597                 }
9598
9599                 if (strchr(tempStats.movelist, '(') != NULL) {
9600                     tempStats.line_is_book = 1;
9601                     tempStats.nr_moves = 0;
9602                     tempStats.moves_left = 0;
9603                 } else {
9604                     tempStats.line_is_book = 0;
9605                 }
9606
9607                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9608                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9609
9610                 SendProgramStatsToFrontend( cps, &tempStats );
9611
9612                 /*
9613                     [AS] Protect the thinkOutput buffer from overflow... this
9614                     is only useful if buf1 hasn't overflowed first!
9615                 */
9616                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9617                          plylev,
9618                          (gameMode == TwoMachinesPlay ?
9619                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9620                          ((double) curscore) / 100.0,
9621                          prefixHint ? lastHint : "",
9622                          prefixHint ? " " : "" );
9623
9624                 if( buf1[0] != NULLCHAR ) {
9625                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9626
9627                     if( strlen(pv) > max_len ) {
9628                         if( appData.debugMode) {
9629                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9630                         }
9631                         pv[max_len+1] = '\0';
9632                     }
9633
9634                     strcat( thinkOutput, pv);
9635                 }
9636
9637                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9638                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9639                     DisplayMove(currentMove - 1);
9640                 }
9641                 return;
9642
9643             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9644                 /* crafty (9.25+) says "(only move) <move>"
9645                  * if there is only 1 legal move
9646                  */
9647                 sscanf(p, "(only move) %s", buf1);
9648                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9649                 sprintf(programStats.movelist, "%s (only move)", buf1);
9650                 programStats.depth = 1;
9651                 programStats.nr_moves = 1;
9652                 programStats.moves_left = 1;
9653                 programStats.nodes = 1;
9654                 programStats.time = 1;
9655                 programStats.got_only_move = 1;
9656
9657                 /* Not really, but we also use this member to
9658                    mean "line isn't going to change" (Crafty
9659                    isn't searching, so stats won't change) */
9660                 programStats.line_is_book = 1;
9661
9662                 SendProgramStatsToFrontend( cps, &programStats );
9663
9664                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9665                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9666                     DisplayMove(currentMove - 1);
9667                 }
9668                 return;
9669             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9670                               &time, &nodes, &plylev, &mvleft,
9671                               &mvtot, mvname) >= 5) {
9672                 /* The stat01: line is from Crafty (9.29+) in response
9673                    to the "." command */
9674                 programStats.seen_stat = 1;
9675                 cps->maybeThinking = TRUE;
9676
9677                 if (programStats.got_only_move || !appData.periodicUpdates)
9678                   return;
9679
9680                 programStats.depth = plylev;
9681                 programStats.time = time;
9682                 programStats.nodes = nodes;
9683                 programStats.moves_left = mvleft;
9684                 programStats.nr_moves = mvtot;
9685                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9686                 programStats.ok_to_send = 1;
9687                 programStats.movelist[0] = '\0';
9688
9689                 SendProgramStatsToFrontend( cps, &programStats );
9690
9691                 return;
9692
9693             } else if (strncmp(message,"++",2) == 0) {
9694                 /* Crafty 9.29+ outputs this */
9695                 programStats.got_fail = 2;
9696                 return;
9697
9698             } else if (strncmp(message,"--",2) == 0) {
9699                 /* Crafty 9.29+ outputs this */
9700                 programStats.got_fail = 1;
9701                 return;
9702
9703             } else if (thinkOutput[0] != NULLCHAR &&
9704                        strncmp(message, "    ", 4) == 0) {
9705                 unsigned message_len;
9706
9707                 p = message;
9708                 while (*p && *p == ' ') p++;
9709
9710                 message_len = strlen( p );
9711
9712                 /* [AS] Avoid buffer overflow */
9713                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9714                     strcat(thinkOutput, " ");
9715                     strcat(thinkOutput, p);
9716                 }
9717
9718                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9719                     strcat(programStats.movelist, " ");
9720                     strcat(programStats.movelist, p);
9721                 }
9722
9723                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9724                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9725                     DisplayMove(currentMove - 1);
9726                 }
9727                 return;
9728             }
9729         }
9730         else {
9731             buf1[0] = NULLCHAR;
9732
9733             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9734                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9735             {
9736                 ChessProgramStats cpstats;
9737
9738                 if (plyext != ' ' && plyext != '\t') {
9739                     time *= 100;
9740                 }
9741
9742                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9743                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9744                     curscore = -curscore;
9745                 }
9746
9747                 cpstats.depth = plylev;
9748                 cpstats.nodes = nodes;
9749                 cpstats.time = time;
9750                 cpstats.score = curscore;
9751                 cpstats.got_only_move = 0;
9752                 cpstats.movelist[0] = '\0';
9753
9754                 if (buf1[0] != NULLCHAR) {
9755                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9756                 }
9757
9758                 cpstats.ok_to_send = 0;
9759                 cpstats.line_is_book = 0;
9760                 cpstats.nr_moves = 0;
9761                 cpstats.moves_left = 0;
9762
9763                 SendProgramStatsToFrontend( cps, &cpstats );
9764             }
9765         }
9766     }
9767 }
9768
9769
9770 /* Parse a game score from the character string "game", and
9771    record it as the history of the current game.  The game
9772    score is NOT assumed to start from the standard position.
9773    The display is not updated in any way.
9774    */
9775 void
9776 ParseGameHistory (char *game)
9777 {
9778     ChessMove moveType;
9779     int fromX, fromY, toX, toY, boardIndex;
9780     char promoChar;
9781     char *p, *q;
9782     char buf[MSG_SIZ];
9783
9784     if (appData.debugMode)
9785       fprintf(debugFP, "Parsing game history: %s\n", game);
9786
9787     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9788     gameInfo.site = StrSave(appData.icsHost);
9789     gameInfo.date = PGNDate();
9790     gameInfo.round = StrSave("-");
9791
9792     /* Parse out names of players */
9793     while (*game == ' ') game++;
9794     p = buf;
9795     while (*game != ' ') *p++ = *game++;
9796     *p = NULLCHAR;
9797     gameInfo.white = StrSave(buf);
9798     while (*game == ' ') game++;
9799     p = buf;
9800     while (*game != ' ' && *game != '\n') *p++ = *game++;
9801     *p = NULLCHAR;
9802     gameInfo.black = StrSave(buf);
9803
9804     /* Parse moves */
9805     boardIndex = blackPlaysFirst ? 1 : 0;
9806     yynewstr(game);
9807     for (;;) {
9808         yyboardindex = boardIndex;
9809         moveType = (ChessMove) Myylex();
9810         switch (moveType) {
9811           case IllegalMove:             /* maybe suicide chess, etc. */
9812   if (appData.debugMode) {
9813     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9814     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9815     setbuf(debugFP, NULL);
9816   }
9817           case WhitePromotion:
9818           case BlackPromotion:
9819           case WhiteNonPromotion:
9820           case BlackNonPromotion:
9821           case NormalMove:
9822           case FirstLeg:
9823           case WhiteCapturesEnPassant:
9824           case BlackCapturesEnPassant:
9825           case WhiteKingSideCastle:
9826           case WhiteQueenSideCastle:
9827           case BlackKingSideCastle:
9828           case BlackQueenSideCastle:
9829           case WhiteKingSideCastleWild:
9830           case WhiteQueenSideCastleWild:
9831           case BlackKingSideCastleWild:
9832           case BlackQueenSideCastleWild:
9833           /* PUSH Fabien */
9834           case WhiteHSideCastleFR:
9835           case WhiteASideCastleFR:
9836           case BlackHSideCastleFR:
9837           case BlackASideCastleFR:
9838           /* POP Fabien */
9839             fromX = currentMoveString[0] - AAA;
9840             fromY = currentMoveString[1] - ONE;
9841             toX = currentMoveString[2] - AAA;
9842             toY = currentMoveString[3] - ONE;
9843             promoChar = currentMoveString[4];
9844             break;
9845           case WhiteDrop:
9846           case BlackDrop:
9847             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9848             fromX = moveType == WhiteDrop ?
9849               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9850             (int) CharToPiece(ToLower(currentMoveString[0]));
9851             fromY = DROP_RANK;
9852             toX = currentMoveString[2] - AAA;
9853             toY = currentMoveString[3] - ONE;
9854             promoChar = NULLCHAR;
9855             break;
9856           case AmbiguousMove:
9857             /* bug? */
9858             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9859   if (appData.debugMode) {
9860     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9861     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9862     setbuf(debugFP, NULL);
9863   }
9864             DisplayError(buf, 0);
9865             return;
9866           case ImpossibleMove:
9867             /* bug? */
9868             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9869   if (appData.debugMode) {
9870     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9871     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9872     setbuf(debugFP, NULL);
9873   }
9874             DisplayError(buf, 0);
9875             return;
9876           case EndOfFile:
9877             if (boardIndex < backwardMostMove) {
9878                 /* Oops, gap.  How did that happen? */
9879                 DisplayError(_("Gap in move list"), 0);
9880                 return;
9881             }
9882             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9883             if (boardIndex > forwardMostMove) {
9884                 forwardMostMove = boardIndex;
9885             }
9886             return;
9887           case ElapsedTime:
9888             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9889                 strcat(parseList[boardIndex-1], " ");
9890                 strcat(parseList[boardIndex-1], yy_text);
9891             }
9892             continue;
9893           case Comment:
9894           case PGNTag:
9895           case NAG:
9896           default:
9897             /* ignore */
9898             continue;
9899           case WhiteWins:
9900           case BlackWins:
9901           case GameIsDrawn:
9902           case GameUnfinished:
9903             if (gameMode == IcsExamining) {
9904                 if (boardIndex < backwardMostMove) {
9905                     /* Oops, gap.  How did that happen? */
9906                     return;
9907                 }
9908                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9909                 return;
9910             }
9911             gameInfo.result = moveType;
9912             p = strchr(yy_text, '{');
9913             if (p == NULL) p = strchr(yy_text, '(');
9914             if (p == NULL) {
9915                 p = yy_text;
9916                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9917             } else {
9918                 q = strchr(p, *p == '{' ? '}' : ')');
9919                 if (q != NULL) *q = NULLCHAR;
9920                 p++;
9921             }
9922             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9923             gameInfo.resultDetails = StrSave(p);
9924             continue;
9925         }
9926         if (boardIndex >= forwardMostMove &&
9927             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9928             backwardMostMove = blackPlaysFirst ? 1 : 0;
9929             return;
9930         }
9931         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9932                                  fromY, fromX, toY, toX, promoChar,
9933                                  parseList[boardIndex]);
9934         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9935         /* currentMoveString is set as a side-effect of yylex */
9936         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9937         strcat(moveList[boardIndex], "\n");
9938         boardIndex++;
9939         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9940         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9941           case MT_NONE:
9942           case MT_STALEMATE:
9943           default:
9944             break;
9945           case MT_CHECK:
9946             if(!IS_SHOGI(gameInfo.variant))
9947                 strcat(parseList[boardIndex - 1], "+");
9948             break;
9949           case MT_CHECKMATE:
9950           case MT_STAINMATE:
9951             strcat(parseList[boardIndex - 1], "#");
9952             break;
9953         }
9954     }
9955 }
9956
9957
9958 /* Apply a move to the given board  */
9959 void
9960 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9961 {
9962   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9963   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9964
9965     /* [HGM] compute & store e.p. status and castling rights for new position */
9966     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9967
9968       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9969       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9970       board[EP_STATUS] = EP_NONE;
9971       board[EP_FILE] = board[EP_RANK] = 100;
9972
9973   if (fromY == DROP_RANK) {
9974         /* must be first */
9975         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9976             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9977             return;
9978         }
9979         piece = board[toY][toX] = (ChessSquare) fromX;
9980   } else {
9981 //      ChessSquare victim;
9982       int i;
9983
9984       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
9985 //           victim = board[killY][killX],
9986            killed = board[killY][killX],
9987            board[killY][killX] = EmptySquare,
9988            board[EP_STATUS] = EP_CAPTURE;
9989            if( kill2X >= 0 && kill2Y >= 0)
9990              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
9991       }
9992
9993       if( board[toY][toX] != EmptySquare ) {
9994            board[EP_STATUS] = EP_CAPTURE;
9995            if( (fromX != toX || fromY != toY) && // not igui!
9996                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9997                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9998                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9999            }
10000       }
10001
10002       pawn = board[fromY][fromX];
10003       if( pawn == WhiteLance || pawn == BlackLance ) {
10004            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10005                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10006                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10007            }
10008       }
10009       if( pawn == WhitePawn ) {
10010            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10011                board[EP_STATUS] = EP_PAWN_MOVE;
10012            if( toY-fromY>=2) {
10013                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10014                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10015                         gameInfo.variant != VariantBerolina || toX < fromX)
10016                       board[EP_STATUS] = toX | berolina;
10017                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10018                         gameInfo.variant != VariantBerolina || toX > fromX)
10019                       board[EP_STATUS] = toX;
10020            }
10021       } else
10022       if( pawn == BlackPawn ) {
10023            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10024                board[EP_STATUS] = EP_PAWN_MOVE;
10025            if( toY-fromY<= -2) {
10026                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10027                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10028                         gameInfo.variant != VariantBerolina || toX < fromX)
10029                       board[EP_STATUS] = toX | berolina;
10030                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10031                         gameInfo.variant != VariantBerolina || toX > fromX)
10032                       board[EP_STATUS] = toX;
10033            }
10034        }
10035
10036        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10037        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10038        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10039        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10040
10041        for(i=0; i<nrCastlingRights; i++) {
10042            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10043               board[CASTLING][i] == toX   && castlingRank[i] == toY
10044              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10045        }
10046
10047        if(gameInfo.variant == VariantSChess) { // update virginity
10048            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10049            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10050            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10051            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10052        }
10053
10054      if (fromX == toX && fromY == toY) return;
10055
10056      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10057      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10058      if(gameInfo.variant == VariantKnightmate)
10059          king += (int) WhiteUnicorn - (int) WhiteKing;
10060
10061     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10062        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10063         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10064         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10065         board[EP_STATUS] = EP_NONE; // capture was fake!
10066     } else
10067     /* Code added by Tord: */
10068     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10069     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10070         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10071       board[EP_STATUS] = EP_NONE; // capture was fake!
10072       board[fromY][fromX] = EmptySquare;
10073       board[toY][toX] = EmptySquare;
10074       if((toX > fromX) != (piece == WhiteRook)) {
10075         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10076       } else {
10077         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10078       }
10079     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10080                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10081       board[EP_STATUS] = EP_NONE;
10082       board[fromY][fromX] = EmptySquare;
10083       board[toY][toX] = EmptySquare;
10084       if((toX > fromX) != (piece == BlackRook)) {
10085         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10086       } else {
10087         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10088       }
10089     /* End of code added by Tord */
10090
10091     } else if (board[fromY][fromX] == king
10092         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10093         && toY == fromY && toX > fromX+1) {
10094         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10095         board[fromY][toX-1] = board[fromY][rookX];
10096         board[fromY][rookX] = EmptySquare;
10097         board[fromY][fromX] = EmptySquare;
10098         board[toY][toX] = king;
10099     } else if (board[fromY][fromX] == king
10100         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10101                && toY == fromY && toX < fromX-1) {
10102         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10103         board[fromY][toX+1] = board[fromY][rookX];
10104         board[fromY][rookX] = EmptySquare;
10105         board[fromY][fromX] = EmptySquare;
10106         board[toY][toX] = king;
10107     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10108                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10109                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10110                ) {
10111         /* white pawn promotion */
10112         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10113         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10114             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10115         board[fromY][fromX] = EmptySquare;
10116     } else if ((fromY >= BOARD_HEIGHT>>1)
10117                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10118                && (toX != fromX)
10119                && gameInfo.variant != VariantXiangqi
10120                && gameInfo.variant != VariantBerolina
10121                && (pawn == WhitePawn)
10122                && (board[toY][toX] == EmptySquare)) {
10123         board[fromY][fromX] = EmptySquare;
10124         board[toY][toX] = piece;
10125         if(toY == epRank - 128 + 1)
10126             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10127         else
10128             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10129     } else if ((fromY == BOARD_HEIGHT-4)
10130                && (toX == fromX)
10131                && gameInfo.variant == VariantBerolina
10132                && (board[fromY][fromX] == WhitePawn)
10133                && (board[toY][toX] == EmptySquare)) {
10134         board[fromY][fromX] = EmptySquare;
10135         board[toY][toX] = WhitePawn;
10136         if(oldEP & EP_BEROLIN_A) {
10137                 captured = board[fromY][fromX-1];
10138                 board[fromY][fromX-1] = EmptySquare;
10139         }else{  captured = board[fromY][fromX+1];
10140                 board[fromY][fromX+1] = EmptySquare;
10141         }
10142     } else if (board[fromY][fromX] == king
10143         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10144                && toY == fromY && toX > fromX+1) {
10145         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10146         board[fromY][toX-1] = board[fromY][rookX];
10147         board[fromY][rookX] = EmptySquare;
10148         board[fromY][fromX] = EmptySquare;
10149         board[toY][toX] = king;
10150     } else if (board[fromY][fromX] == king
10151         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10152                && toY == fromY && toX < fromX-1) {
10153         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10154         board[fromY][toX+1] = board[fromY][rookX];
10155         board[fromY][rookX] = EmptySquare;
10156         board[fromY][fromX] = EmptySquare;
10157         board[toY][toX] = king;
10158     } else if (fromY == 7 && fromX == 3
10159                && board[fromY][fromX] == BlackKing
10160                && toY == 7 && toX == 5) {
10161         board[fromY][fromX] = EmptySquare;
10162         board[toY][toX] = BlackKing;
10163         board[fromY][7] = EmptySquare;
10164         board[toY][4] = BlackRook;
10165     } else if (fromY == 7 && fromX == 3
10166                && board[fromY][fromX] == BlackKing
10167                && toY == 7 && toX == 1) {
10168         board[fromY][fromX] = EmptySquare;
10169         board[toY][toX] = BlackKing;
10170         board[fromY][0] = EmptySquare;
10171         board[toY][2] = BlackRook;
10172     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10173                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10174                && toY < promoRank && promoChar
10175                ) {
10176         /* black pawn promotion */
10177         board[toY][toX] = CharToPiece(ToLower(promoChar));
10178         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10179             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10180         board[fromY][fromX] = EmptySquare;
10181     } else if ((fromY < BOARD_HEIGHT>>1)
10182                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10183                && (toX != fromX)
10184                && gameInfo.variant != VariantXiangqi
10185                && gameInfo.variant != VariantBerolina
10186                && (pawn == BlackPawn)
10187                && (board[toY][toX] == EmptySquare)) {
10188         board[fromY][fromX] = EmptySquare;
10189         board[toY][toX] = piece;
10190         if(toY == epRank - 128 - 1)
10191             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10192         else
10193             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10194     } else if ((fromY == 3)
10195                && (toX == fromX)
10196                && gameInfo.variant == VariantBerolina
10197                && (board[fromY][fromX] == BlackPawn)
10198                && (board[toY][toX] == EmptySquare)) {
10199         board[fromY][fromX] = EmptySquare;
10200         board[toY][toX] = BlackPawn;
10201         if(oldEP & EP_BEROLIN_A) {
10202                 captured = board[fromY][fromX-1];
10203                 board[fromY][fromX-1] = EmptySquare;
10204         }else{  captured = board[fromY][fromX+1];
10205                 board[fromY][fromX+1] = EmptySquare;
10206         }
10207     } else {
10208         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10209         board[fromY][fromX] = EmptySquare;
10210         board[toY][toX] = piece;
10211     }
10212   }
10213
10214     if (gameInfo.holdingsWidth != 0) {
10215
10216       /* !!A lot more code needs to be written to support holdings  */
10217       /* [HGM] OK, so I have written it. Holdings are stored in the */
10218       /* penultimate board files, so they are automaticlly stored   */
10219       /* in the game history.                                       */
10220       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10221                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10222         /* Delete from holdings, by decreasing count */
10223         /* and erasing image if necessary            */
10224         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10225         if(p < (int) BlackPawn) { /* white drop */
10226              p -= (int)WhitePawn;
10227                  p = PieceToNumber((ChessSquare)p);
10228              if(p >= gameInfo.holdingsSize) p = 0;
10229              if(--board[p][BOARD_WIDTH-2] <= 0)
10230                   board[p][BOARD_WIDTH-1] = EmptySquare;
10231              if((int)board[p][BOARD_WIDTH-2] < 0)
10232                         board[p][BOARD_WIDTH-2] = 0;
10233         } else {                  /* black drop */
10234              p -= (int)BlackPawn;
10235                  p = PieceToNumber((ChessSquare)p);
10236              if(p >= gameInfo.holdingsSize) p = 0;
10237              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10238                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10239              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10240                         board[BOARD_HEIGHT-1-p][1] = 0;
10241         }
10242       }
10243       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10244           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10245         /* [HGM] holdings: Add to holdings, if holdings exist */
10246         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10247                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10248                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10249         }
10250         p = (int) captured;
10251         if (p >= (int) BlackPawn) {
10252           p -= (int)BlackPawn;
10253           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10254                   /* Restore shogi-promoted piece to its original  first */
10255                   captured = (ChessSquare) (DEMOTED captured);
10256                   p = DEMOTED p;
10257           }
10258           p = PieceToNumber((ChessSquare)p);
10259           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10260           board[p][BOARD_WIDTH-2]++;
10261           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10262         } else {
10263           p -= (int)WhitePawn;
10264           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10265                   captured = (ChessSquare) (DEMOTED captured);
10266                   p = DEMOTED p;
10267           }
10268           p = PieceToNumber((ChessSquare)p);
10269           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10270           board[BOARD_HEIGHT-1-p][1]++;
10271           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10272         }
10273       }
10274     } else if (gameInfo.variant == VariantAtomic) {
10275       if (captured != EmptySquare) {
10276         int y, x;
10277         for (y = toY-1; y <= toY+1; y++) {
10278           for (x = toX-1; x <= toX+1; x++) {
10279             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10280                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10281               board[y][x] = EmptySquare;
10282             }
10283           }
10284         }
10285         board[toY][toX] = EmptySquare;
10286       }
10287     }
10288
10289     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10290         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10291     } else
10292     if(promoChar == '+') {
10293         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10294         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10295         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10296           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10297     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10298         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10299         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10300            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10301         board[toY][toX] = newPiece;
10302     }
10303     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10304                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10305         // [HGM] superchess: take promotion piece out of holdings
10306         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10307         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10308             if(!--board[k][BOARD_WIDTH-2])
10309                 board[k][BOARD_WIDTH-1] = EmptySquare;
10310         } else {
10311             if(!--board[BOARD_HEIGHT-1-k][1])
10312                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10313         }
10314     }
10315 }
10316
10317 /* Updates forwardMostMove */
10318 void
10319 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10320 {
10321     int x = toX, y = toY;
10322     char *s = parseList[forwardMostMove];
10323     ChessSquare p = boards[forwardMostMove][toY][toX];
10324 //    forwardMostMove++; // [HGM] bare: moved downstream
10325
10326     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10327     (void) CoordsToAlgebraic(boards[forwardMostMove],
10328                              PosFlags(forwardMostMove),
10329                              fromY, fromX, y, x, promoChar,
10330                              s);
10331     if(killX >= 0 && killY >= 0)
10332         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10333
10334     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10335         int timeLeft; static int lastLoadFlag=0; int king, piece;
10336         piece = boards[forwardMostMove][fromY][fromX];
10337         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10338         if(gameInfo.variant == VariantKnightmate)
10339             king += (int) WhiteUnicorn - (int) WhiteKing;
10340         if(forwardMostMove == 0) {
10341             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10342                 fprintf(serverMoves, "%s;", UserName());
10343             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10344                 fprintf(serverMoves, "%s;", second.tidy);
10345             fprintf(serverMoves, "%s;", first.tidy);
10346             if(gameMode == MachinePlaysWhite)
10347                 fprintf(serverMoves, "%s;", UserName());
10348             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10349                 fprintf(serverMoves, "%s;", second.tidy);
10350         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10351         lastLoadFlag = loadFlag;
10352         // print base move
10353         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10354         // print castling suffix
10355         if( toY == fromY && piece == king ) {
10356             if(toX-fromX > 1)
10357                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10358             if(fromX-toX >1)
10359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10360         }
10361         // e.p. suffix
10362         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10363              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10364              boards[forwardMostMove][toY][toX] == EmptySquare
10365              && fromX != toX && fromY != toY)
10366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10367         // promotion suffix
10368         if(promoChar != NULLCHAR) {
10369             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10370                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10371                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10372             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10373         }
10374         if(!loadFlag) {
10375                 char buf[MOVE_LEN*2], *p; int len;
10376             fprintf(serverMoves, "/%d/%d",
10377                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10378             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10379             else                      timeLeft = blackTimeRemaining/1000;
10380             fprintf(serverMoves, "/%d", timeLeft);
10381                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10382                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10383                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10384                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10385             fprintf(serverMoves, "/%s", buf);
10386         }
10387         fflush(serverMoves);
10388     }
10389
10390     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10391         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10392       return;
10393     }
10394     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10395     if (commentList[forwardMostMove+1] != NULL) {
10396         free(commentList[forwardMostMove+1]);
10397         commentList[forwardMostMove+1] = NULL;
10398     }
10399     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10400     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10401     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10402     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10403     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10404     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10405     adjustedClock = FALSE;
10406     gameInfo.result = GameUnfinished;
10407     if (gameInfo.resultDetails != NULL) {
10408         free(gameInfo.resultDetails);
10409         gameInfo.resultDetails = NULL;
10410     }
10411     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10412                               moveList[forwardMostMove - 1]);
10413     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10414       case MT_NONE:
10415       case MT_STALEMATE:
10416       default:
10417         break;
10418       case MT_CHECK:
10419         if(!IS_SHOGI(gameInfo.variant))
10420             strcat(parseList[forwardMostMove - 1], "+");
10421         break;
10422       case MT_CHECKMATE:
10423       case MT_STAINMATE:
10424         strcat(parseList[forwardMostMove - 1], "#");
10425         break;
10426     }
10427 }
10428
10429 /* Updates currentMove if not pausing */
10430 void
10431 ShowMove (int fromX, int fromY, int toX, int toY)
10432 {
10433     int instant = (gameMode == PlayFromGameFile) ?
10434         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10435     if(appData.noGUI) return;
10436     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10437         if (!instant) {
10438             if (forwardMostMove == currentMove + 1) {
10439                 AnimateMove(boards[forwardMostMove - 1],
10440                             fromX, fromY, toX, toY);
10441             }
10442         }
10443         currentMove = forwardMostMove;
10444     }
10445
10446     killX = killY = -1; // [HGM] lion: used up
10447
10448     if (instant) return;
10449
10450     DisplayMove(currentMove - 1);
10451     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10452             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10453                 SetHighlights(fromX, fromY, toX, toY);
10454             }
10455     }
10456     DrawPosition(FALSE, boards[currentMove]);
10457     DisplayBothClocks();
10458     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10459 }
10460
10461 void
10462 SendEgtPath (ChessProgramState *cps)
10463 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10464         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10465
10466         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10467
10468         while(*p) {
10469             char c, *q = name+1, *r, *s;
10470
10471             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10472             while(*p && *p != ',') *q++ = *p++;
10473             *q++ = ':'; *q = 0;
10474             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10475                 strcmp(name, ",nalimov:") == 0 ) {
10476                 // take nalimov path from the menu-changeable option first, if it is defined
10477               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10478                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10479             } else
10480             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10481                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10482                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10483                 s = r = StrStr(s, ":") + 1; // beginning of path info
10484                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10485                 c = *r; *r = 0;             // temporarily null-terminate path info
10486                     *--q = 0;               // strip of trailig ':' from name
10487                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10488                 *r = c;
10489                 SendToProgram(buf,cps);     // send egtbpath command for this format
10490             }
10491             if(*p == ',') p++; // read away comma to position for next format name
10492         }
10493 }
10494
10495 static int
10496 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10497 {
10498       int width = 8, height = 8, holdings = 0;             // most common sizes
10499       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10500       // correct the deviations default for each variant
10501       if( v == VariantXiangqi ) width = 9,  height = 10;
10502       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10503       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10504       if( v == VariantCapablanca || v == VariantCapaRandom ||
10505           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10506                                 width = 10;
10507       if( v == VariantCourier ) width = 12;
10508       if( v == VariantSuper )                            holdings = 8;
10509       if( v == VariantGreat )   width = 10,              holdings = 8;
10510       if( v == VariantSChess )                           holdings = 7;
10511       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10512       if( v == VariantChuChess) width = 10, height = 10;
10513       if( v == VariantChu )     width = 12, height = 12;
10514       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10515              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10516              holdingsSize >= 0 && holdingsSize != holdings;
10517 }
10518
10519 char variantError[MSG_SIZ];
10520
10521 char *
10522 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10523 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10524       char *p, *variant = VariantName(v);
10525       static char b[MSG_SIZ];
10526       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10527            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10528                                                holdingsSize, variant); // cook up sized variant name
10529            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10530            if(StrStr(list, b) == NULL) {
10531                // specific sized variant not known, check if general sizing allowed
10532                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10533                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10534                             boardWidth, boardHeight, holdingsSize, engine);
10535                    return NULL;
10536                }
10537                /* [HGM] here we really should compare with the maximum supported board size */
10538            }
10539       } else snprintf(b, MSG_SIZ,"%s", variant);
10540       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10541       p = StrStr(list, b);
10542       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10543       if(p == NULL) {
10544           // occurs not at all in list, or only as sub-string
10545           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10546           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10547               int l = strlen(variantError);
10548               char *q;
10549               while(p != list && p[-1] != ',') p--;
10550               q = strchr(p, ',');
10551               if(q) *q = NULLCHAR;
10552               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10553               if(q) *q= ',';
10554           }
10555           return NULL;
10556       }
10557       return b;
10558 }
10559
10560 void
10561 InitChessProgram (ChessProgramState *cps, int setup)
10562 /* setup needed to setup FRC opening position */
10563 {
10564     char buf[MSG_SIZ], *b;
10565     if (appData.noChessProgram) return;
10566     hintRequested = FALSE;
10567     bookRequested = FALSE;
10568
10569     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10570     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10571     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10572     if(cps->memSize) { /* [HGM] memory */
10573       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10574         SendToProgram(buf, cps);
10575     }
10576     SendEgtPath(cps); /* [HGM] EGT */
10577     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10578       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10579         SendToProgram(buf, cps);
10580     }
10581
10582     setboardSpoiledMachineBlack = FALSE;
10583     SendToProgram(cps->initString, cps);
10584     if (gameInfo.variant != VariantNormal &&
10585         gameInfo.variant != VariantLoadable
10586         /* [HGM] also send variant if board size non-standard */
10587         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10588
10589       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10590                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10591       if (b == NULL) {
10592         VariantClass v;
10593         char c, *q = cps->variants, *p = strchr(q, ',');
10594         if(p) *p = NULLCHAR;
10595         v = StringToVariant(q);
10596         DisplayError(variantError, 0);
10597         if(v != VariantUnknown && cps == &first) {
10598             int w, h, s;
10599             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10600                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10601             ASSIGN(appData.variant, q);
10602             Reset(TRUE, FALSE);
10603         }
10604         if(p) *p = ',';
10605         return;
10606       }
10607
10608       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10609       SendToProgram(buf, cps);
10610     }
10611     currentlyInitializedVariant = gameInfo.variant;
10612
10613     /* [HGM] send opening position in FRC to first engine */
10614     if(setup) {
10615           SendToProgram("force\n", cps);
10616           SendBoard(cps, 0);
10617           /* engine is now in force mode! Set flag to wake it up after first move. */
10618           setboardSpoiledMachineBlack = 1;
10619     }
10620
10621     if (cps->sendICS) {
10622       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10623       SendToProgram(buf, cps);
10624     }
10625     cps->maybeThinking = FALSE;
10626     cps->offeredDraw = 0;
10627     if (!appData.icsActive) {
10628         SendTimeControl(cps, movesPerSession, timeControl,
10629                         timeIncrement, appData.searchDepth,
10630                         searchTime);
10631     }
10632     if (appData.showThinking
10633         // [HGM] thinking: four options require thinking output to be sent
10634         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10635                                 ) {
10636         SendToProgram("post\n", cps);
10637     }
10638     SendToProgram("hard\n", cps);
10639     if (!appData.ponderNextMove) {
10640         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10641            it without being sure what state we are in first.  "hard"
10642            is not a toggle, so that one is OK.
10643          */
10644         SendToProgram("easy\n", cps);
10645     }
10646     if (cps->usePing) {
10647       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10648       SendToProgram(buf, cps);
10649     }
10650     cps->initDone = TRUE;
10651     ClearEngineOutputPane(cps == &second);
10652 }
10653
10654
10655 void
10656 ResendOptions (ChessProgramState *cps)
10657 { // send the stored value of the options
10658   int i;
10659   char buf[MSG_SIZ];
10660   Option *opt = cps->option;
10661   for(i=0; i<cps->nrOptions; i++, opt++) {
10662       switch(opt->type) {
10663         case Spin:
10664         case Slider:
10665         case CheckBox:
10666             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10667           break;
10668         case ComboBox:
10669           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10670           break;
10671         default:
10672             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10673           break;
10674         case Button:
10675         case SaveButton:
10676           continue;
10677       }
10678       SendToProgram(buf, cps);
10679   }
10680 }
10681
10682 void
10683 StartChessProgram (ChessProgramState *cps)
10684 {
10685     char buf[MSG_SIZ];
10686     int err;
10687
10688     if (appData.noChessProgram) return;
10689     cps->initDone = FALSE;
10690
10691     if (strcmp(cps->host, "localhost") == 0) {
10692         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10693     } else if (*appData.remoteShell == NULLCHAR) {
10694         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10695     } else {
10696         if (*appData.remoteUser == NULLCHAR) {
10697           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10698                     cps->program);
10699         } else {
10700           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10701                     cps->host, appData.remoteUser, cps->program);
10702         }
10703         err = StartChildProcess(buf, "", &cps->pr);
10704     }
10705
10706     if (err != 0) {
10707       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10708         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10709         if(cps != &first) return;
10710         appData.noChessProgram = TRUE;
10711         ThawUI();
10712         SetNCPMode();
10713 //      DisplayFatalError(buf, err, 1);
10714 //      cps->pr = NoProc;
10715 //      cps->isr = NULL;
10716         return;
10717     }
10718
10719     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10720     if (cps->protocolVersion > 1) {
10721       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10722       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10723         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10724         cps->comboCnt = 0;  //                and values of combo boxes
10725       }
10726       SendToProgram(buf, cps);
10727       if(cps->reload) ResendOptions(cps);
10728     } else {
10729       SendToProgram("xboard\n", cps);
10730     }
10731 }
10732
10733 void
10734 TwoMachinesEventIfReady P((void))
10735 {
10736   static int curMess = 0;
10737   if (first.lastPing != first.lastPong) {
10738     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10739     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10740     return;
10741   }
10742   if (second.lastPing != second.lastPong) {
10743     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10744     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10745     return;
10746   }
10747   DisplayMessage("", ""); curMess = 0;
10748   TwoMachinesEvent();
10749 }
10750
10751 char *
10752 MakeName (char *template)
10753 {
10754     time_t clock;
10755     struct tm *tm;
10756     static char buf[MSG_SIZ];
10757     char *p = buf;
10758     int i;
10759
10760     clock = time((time_t *)NULL);
10761     tm = localtime(&clock);
10762
10763     while(*p++ = *template++) if(p[-1] == '%') {
10764         switch(*template++) {
10765           case 0:   *p = 0; return buf;
10766           case 'Y': i = tm->tm_year+1900; break;
10767           case 'y': i = tm->tm_year-100; break;
10768           case 'M': i = tm->tm_mon+1; break;
10769           case 'd': i = tm->tm_mday; break;
10770           case 'h': i = tm->tm_hour; break;
10771           case 'm': i = tm->tm_min; break;
10772           case 's': i = tm->tm_sec; break;
10773           default:  i = 0;
10774         }
10775         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10776     }
10777     return buf;
10778 }
10779
10780 int
10781 CountPlayers (char *p)
10782 {
10783     int n = 0;
10784     while(p = strchr(p, '\n')) p++, n++; // count participants
10785     return n;
10786 }
10787
10788 FILE *
10789 WriteTourneyFile (char *results, FILE *f)
10790 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10791     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10792     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10793         // create a file with tournament description
10794         fprintf(f, "-participants {%s}\n", appData.participants);
10795         fprintf(f, "-seedBase %d\n", appData.seedBase);
10796         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10797         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10798         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10799         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10800         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10801         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10802         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10803         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10804         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10805         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10806         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10807         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10808         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10809         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10810         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10811         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10812         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10813         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10814         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10815         fprintf(f, "-smpCores %d\n", appData.smpCores);
10816         if(searchTime > 0)
10817                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10818         else {
10819                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10820                 fprintf(f, "-tc %s\n", appData.timeControl);
10821                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10822         }
10823         fprintf(f, "-results \"%s\"\n", results);
10824     }
10825     return f;
10826 }
10827
10828 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10829
10830 void
10831 Substitute (char *participants, int expunge)
10832 {
10833     int i, changed, changes=0, nPlayers=0;
10834     char *p, *q, *r, buf[MSG_SIZ];
10835     if(participants == NULL) return;
10836     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10837     r = p = participants; q = appData.participants;
10838     while(*p && *p == *q) {
10839         if(*p == '\n') r = p+1, nPlayers++;
10840         p++; q++;
10841     }
10842     if(*p) { // difference
10843         while(*p && *p++ != '\n');
10844         while(*q && *q++ != '\n');
10845       changed = nPlayers;
10846         changes = 1 + (strcmp(p, q) != 0);
10847     }
10848     if(changes == 1) { // a single engine mnemonic was changed
10849         q = r; while(*q) nPlayers += (*q++ == '\n');
10850         p = buf; while(*r && (*p = *r++) != '\n') p++;
10851         *p = NULLCHAR;
10852         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10853         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10854         if(mnemonic[i]) { // The substitute is valid
10855             FILE *f;
10856             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10857                 flock(fileno(f), LOCK_EX);
10858                 ParseArgsFromFile(f);
10859                 fseek(f, 0, SEEK_SET);
10860                 FREE(appData.participants); appData.participants = participants;
10861                 if(expunge) { // erase results of replaced engine
10862                     int len = strlen(appData.results), w, b, dummy;
10863                     for(i=0; i<len; i++) {
10864                         Pairing(i, nPlayers, &w, &b, &dummy);
10865                         if((w == changed || b == changed) && appData.results[i] == '*') {
10866                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10867                             fclose(f);
10868                             return;
10869                         }
10870                     }
10871                     for(i=0; i<len; i++) {
10872                         Pairing(i, nPlayers, &w, &b, &dummy);
10873                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10874                     }
10875                 }
10876                 WriteTourneyFile(appData.results, f);
10877                 fclose(f); // release lock
10878                 return;
10879             }
10880         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10881     }
10882     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10883     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10884     free(participants);
10885     return;
10886 }
10887
10888 int
10889 CheckPlayers (char *participants)
10890 {
10891         int i;
10892         char buf[MSG_SIZ], *p;
10893         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10894         while(p = strchr(participants, '\n')) {
10895             *p = NULLCHAR;
10896             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10897             if(!mnemonic[i]) {
10898                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10899                 *p = '\n';
10900                 DisplayError(buf, 0);
10901                 return 1;
10902             }
10903             *p = '\n';
10904             participants = p + 1;
10905         }
10906         return 0;
10907 }
10908
10909 int
10910 CreateTourney (char *name)
10911 {
10912         FILE *f;
10913         if(matchMode && strcmp(name, appData.tourneyFile)) {
10914              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10915         }
10916         if(name[0] == NULLCHAR) {
10917             if(appData.participants[0])
10918                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10919             return 0;
10920         }
10921         f = fopen(name, "r");
10922         if(f) { // file exists
10923             ASSIGN(appData.tourneyFile, name);
10924             ParseArgsFromFile(f); // parse it
10925         } else {
10926             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10927             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10928                 DisplayError(_("Not enough participants"), 0);
10929                 return 0;
10930             }
10931             if(CheckPlayers(appData.participants)) return 0;
10932             ASSIGN(appData.tourneyFile, name);
10933             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10934             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10935         }
10936         fclose(f);
10937         appData.noChessProgram = FALSE;
10938         appData.clockMode = TRUE;
10939         SetGNUMode();
10940         return 1;
10941 }
10942
10943 int
10944 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10945 {
10946     char buf[MSG_SIZ], *p, *q;
10947     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10948     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10949     skip = !all && group[0]; // if group requested, we start in skip mode
10950     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10951         p = names; q = buf; header = 0;
10952         while(*p && *p != '\n') *q++ = *p++;
10953         *q = 0;
10954         if(*p == '\n') p++;
10955         if(buf[0] == '#') {
10956             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10957             depth++; // we must be entering a new group
10958             if(all) continue; // suppress printing group headers when complete list requested
10959             header = 1;
10960             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10961         }
10962         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10963         if(engineList[i]) free(engineList[i]);
10964         engineList[i] = strdup(buf);
10965         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10966         if(engineMnemonic[i]) free(engineMnemonic[i]);
10967         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10968             strcat(buf, " (");
10969             sscanf(q + 8, "%s", buf + strlen(buf));
10970             strcat(buf, ")");
10971         }
10972         engineMnemonic[i] = strdup(buf);
10973         i++;
10974     }
10975     engineList[i] = engineMnemonic[i] = NULL;
10976     return i;
10977 }
10978
10979 // following implemented as macro to avoid type limitations
10980 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10981
10982 void
10983 SwapEngines (int n)
10984 {   // swap settings for first engine and other engine (so far only some selected options)
10985     int h;
10986     char *p;
10987     if(n == 0) return;
10988     SWAP(directory, p)
10989     SWAP(chessProgram, p)
10990     SWAP(isUCI, h)
10991     SWAP(hasOwnBookUCI, h)
10992     SWAP(protocolVersion, h)
10993     SWAP(reuse, h)
10994     SWAP(scoreIsAbsolute, h)
10995     SWAP(timeOdds, h)
10996     SWAP(logo, p)
10997     SWAP(pgnName, p)
10998     SWAP(pvSAN, h)
10999     SWAP(engOptions, p)
11000     SWAP(engInitString, p)
11001     SWAP(computerString, p)
11002     SWAP(features, p)
11003     SWAP(fenOverride, p)
11004     SWAP(NPS, h)
11005     SWAP(accumulateTC, h)
11006     SWAP(drawDepth, h)
11007     SWAP(host, p)
11008     SWAP(pseudo, h)
11009 }
11010
11011 int
11012 GetEngineLine (char *s, int n)
11013 {
11014     int i;
11015     char buf[MSG_SIZ];
11016     extern char *icsNames;
11017     if(!s || !*s) return 0;
11018     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11019     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11020     if(!mnemonic[i]) return 0;
11021     if(n == 11) return 1; // just testing if there was a match
11022     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11023     if(n == 1) SwapEngines(n);
11024     ParseArgsFromString(buf);
11025     if(n == 1) SwapEngines(n);
11026     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11027         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11028         ParseArgsFromString(buf);
11029     }
11030     return 1;
11031 }
11032
11033 int
11034 SetPlayer (int player, char *p)
11035 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11036     int i;
11037     char buf[MSG_SIZ], *engineName;
11038     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11039     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11040     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11041     if(mnemonic[i]) {
11042         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11043         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11044         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11045         ParseArgsFromString(buf);
11046     } else { // no engine with this nickname is installed!
11047         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11048         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11049         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11050         ModeHighlight();
11051         DisplayError(buf, 0);
11052         return 0;
11053     }
11054     free(engineName);
11055     return i;
11056 }
11057
11058 char *recentEngines;
11059
11060 void
11061 RecentEngineEvent (int nr)
11062 {
11063     int n;
11064 //    SwapEngines(1); // bump first to second
11065 //    ReplaceEngine(&second, 1); // and load it there
11066     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11067     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11068     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11069         ReplaceEngine(&first, 0);
11070         FloatToFront(&appData.recentEngineList, command[n]);
11071     }
11072 }
11073
11074 int
11075 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11076 {   // determine players from game number
11077     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11078
11079     if(appData.tourneyType == 0) {
11080         roundsPerCycle = (nPlayers - 1) | 1;
11081         pairingsPerRound = nPlayers / 2;
11082     } else if(appData.tourneyType > 0) {
11083         roundsPerCycle = nPlayers - appData.tourneyType;
11084         pairingsPerRound = appData.tourneyType;
11085     }
11086     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11087     gamesPerCycle = gamesPerRound * roundsPerCycle;
11088     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11089     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11090     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11091     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11092     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11093     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11094
11095     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11096     if(appData.roundSync) *syncInterval = gamesPerRound;
11097
11098     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11099
11100     if(appData.tourneyType == 0) {
11101         if(curPairing == (nPlayers-1)/2 ) {
11102             *whitePlayer = curRound;
11103             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11104         } else {
11105             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11106             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11107             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11108             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11109         }
11110     } else if(appData.tourneyType > 1) {
11111         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11112         *whitePlayer = curRound + appData.tourneyType;
11113     } else if(appData.tourneyType > 0) {
11114         *whitePlayer = curPairing;
11115         *blackPlayer = curRound + appData.tourneyType;
11116     }
11117
11118     // take care of white/black alternation per round.
11119     // For cycles and games this is already taken care of by default, derived from matchGame!
11120     return curRound & 1;
11121 }
11122
11123 int
11124 NextTourneyGame (int nr, int *swapColors)
11125 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11126     char *p, *q;
11127     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11128     FILE *tf;
11129     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11130     tf = fopen(appData.tourneyFile, "r");
11131     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11132     ParseArgsFromFile(tf); fclose(tf);
11133     InitTimeControls(); // TC might be altered from tourney file
11134
11135     nPlayers = CountPlayers(appData.participants); // count participants
11136     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11137     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11138
11139     if(syncInterval) {
11140         p = q = appData.results;
11141         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11142         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11143             DisplayMessage(_("Waiting for other game(s)"),"");
11144             waitingForGame = TRUE;
11145             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11146             return 0;
11147         }
11148         waitingForGame = FALSE;
11149     }
11150
11151     if(appData.tourneyType < 0) {
11152         if(nr>=0 && !pairingReceived) {
11153             char buf[1<<16];
11154             if(pairing.pr == NoProc) {
11155                 if(!appData.pairingEngine[0]) {
11156                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11157                     return 0;
11158                 }
11159                 StartChessProgram(&pairing); // starts the pairing engine
11160             }
11161             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11162             SendToProgram(buf, &pairing);
11163             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11164             SendToProgram(buf, &pairing);
11165             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11166         }
11167         pairingReceived = 0;                              // ... so we continue here
11168         *swapColors = 0;
11169         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11170         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11171         matchGame = 1; roundNr = nr / syncInterval + 1;
11172     }
11173
11174     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11175
11176     // redefine engines, engine dir, etc.
11177     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11178     if(first.pr == NoProc) {
11179       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11180       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11181     }
11182     if(second.pr == NoProc) {
11183       SwapEngines(1);
11184       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11185       SwapEngines(1);         // and make that valid for second engine by swapping
11186       InitEngine(&second, 1);
11187     }
11188     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11189     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11190     return OK;
11191 }
11192
11193 void
11194 NextMatchGame ()
11195 {   // performs game initialization that does not invoke engines, and then tries to start the game
11196     int res, firstWhite, swapColors = 0;
11197     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11198     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
11199         char buf[MSG_SIZ];
11200         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11201         if(strcmp(buf, currentDebugFile)) { // name has changed
11202             FILE *f = fopen(buf, "w");
11203             if(f) { // if opening the new file failed, just keep using the old one
11204                 ASSIGN(currentDebugFile, buf);
11205                 fclose(debugFP);
11206                 debugFP = f;
11207             }
11208             if(appData.serverFileName) {
11209                 if(serverFP) fclose(serverFP);
11210                 serverFP = fopen(appData.serverFileName, "w");
11211                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11212                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11213             }
11214         }
11215     }
11216     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11217     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11218     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11219     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11220     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11221     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11222     Reset(FALSE, first.pr != NoProc);
11223     res = LoadGameOrPosition(matchGame); // setup game
11224     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11225     if(!res) return; // abort when bad game/pos file
11226     TwoMachinesEvent();
11227 }
11228
11229 void
11230 UserAdjudicationEvent (int result)
11231 {
11232     ChessMove gameResult = GameIsDrawn;
11233
11234     if( result > 0 ) {
11235         gameResult = WhiteWins;
11236     }
11237     else if( result < 0 ) {
11238         gameResult = BlackWins;
11239     }
11240
11241     if( gameMode == TwoMachinesPlay ) {
11242         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11243     }
11244 }
11245
11246
11247 // [HGM] save: calculate checksum of game to make games easily identifiable
11248 int
11249 StringCheckSum (char *s)
11250 {
11251         int i = 0;
11252         if(s==NULL) return 0;
11253         while(*s) i = i*259 + *s++;
11254         return i;
11255 }
11256
11257 int
11258 GameCheckSum ()
11259 {
11260         int i, sum=0;
11261         for(i=backwardMostMove; i<forwardMostMove; i++) {
11262                 sum += pvInfoList[i].depth;
11263                 sum += StringCheckSum(parseList[i]);
11264                 sum += StringCheckSum(commentList[i]);
11265                 sum *= 261;
11266         }
11267         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11268         return sum + StringCheckSum(commentList[i]);
11269 } // end of save patch
11270
11271 void
11272 GameEnds (ChessMove result, char *resultDetails, int whosays)
11273 {
11274     GameMode nextGameMode;
11275     int isIcsGame;
11276     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11277
11278     if(endingGame) return; /* [HGM] crash: forbid recursion */
11279     endingGame = 1;
11280     if(twoBoards) { // [HGM] dual: switch back to one board
11281         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11282         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11283     }
11284     if (appData.debugMode) {
11285       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11286               result, resultDetails ? resultDetails : "(null)", whosays);
11287     }
11288
11289     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11290
11291     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11292
11293     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11294         /* If we are playing on ICS, the server decides when the
11295            game is over, but the engine can offer to draw, claim
11296            a draw, or resign.
11297          */
11298 #if ZIPPY
11299         if (appData.zippyPlay && first.initDone) {
11300             if (result == GameIsDrawn) {
11301                 /* In case draw still needs to be claimed */
11302                 SendToICS(ics_prefix);
11303                 SendToICS("draw\n");
11304             } else if (StrCaseStr(resultDetails, "resign")) {
11305                 SendToICS(ics_prefix);
11306                 SendToICS("resign\n");
11307             }
11308         }
11309 #endif
11310         endingGame = 0; /* [HGM] crash */
11311         return;
11312     }
11313
11314     /* If we're loading the game from a file, stop */
11315     if (whosays == GE_FILE) {
11316       (void) StopLoadGameTimer();
11317       gameFileFP = NULL;
11318     }
11319
11320     /* Cancel draw offers */
11321     first.offeredDraw = second.offeredDraw = 0;
11322
11323     /* If this is an ICS game, only ICS can really say it's done;
11324        if not, anyone can. */
11325     isIcsGame = (gameMode == IcsPlayingWhite ||
11326                  gameMode == IcsPlayingBlack ||
11327                  gameMode == IcsObserving    ||
11328                  gameMode == IcsExamining);
11329
11330     if (!isIcsGame || whosays == GE_ICS) {
11331         /* OK -- not an ICS game, or ICS said it was done */
11332         StopClocks();
11333         if (!isIcsGame && !appData.noChessProgram)
11334           SetUserThinkingEnables();
11335
11336         /* [HGM] if a machine claims the game end we verify this claim */
11337         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11338             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11339                 char claimer;
11340                 ChessMove trueResult = (ChessMove) -1;
11341
11342                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11343                                             first.twoMachinesColor[0] :
11344                                             second.twoMachinesColor[0] ;
11345
11346                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11347                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11348                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11349                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11350                 } else
11351                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11352                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11353                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11354                 } else
11355                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11356                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11357                 }
11358
11359                 // now verify win claims, but not in drop games, as we don't understand those yet
11360                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11361                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11362                     (result == WhiteWins && claimer == 'w' ||
11363                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11364                       if (appData.debugMode) {
11365                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11366                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11367                       }
11368                       if(result != trueResult) {
11369                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11370                               result = claimer == 'w' ? BlackWins : WhiteWins;
11371                               resultDetails = buf;
11372                       }
11373                 } else
11374                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11375                     && (forwardMostMove <= backwardMostMove ||
11376                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11377                         (claimer=='b')==(forwardMostMove&1))
11378                                                                                   ) {
11379                       /* [HGM] verify: draws that were not flagged are false claims */
11380                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11381                       result = claimer == 'w' ? BlackWins : WhiteWins;
11382                       resultDetails = buf;
11383                 }
11384                 /* (Claiming a loss is accepted no questions asked!) */
11385             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11386                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11387                 result = GameUnfinished;
11388                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11389             }
11390             /* [HGM] bare: don't allow bare King to win */
11391             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11392                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11393                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11394                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11395                && result != GameIsDrawn)
11396             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11397                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11398                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11399                         if(p >= 0 && p <= (int)WhiteKing) k++;
11400                 }
11401                 if (appData.debugMode) {
11402                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11403                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11404                 }
11405                 if(k <= 1) {
11406                         result = GameIsDrawn;
11407                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11408                         resultDetails = buf;
11409                 }
11410             }
11411         }
11412
11413
11414         if(serverMoves != NULL && !loadFlag) { char c = '=';
11415             if(result==WhiteWins) c = '+';
11416             if(result==BlackWins) c = '-';
11417             if(resultDetails != NULL)
11418                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11419         }
11420         if (resultDetails != NULL) {
11421             gameInfo.result = result;
11422             gameInfo.resultDetails = StrSave(resultDetails);
11423
11424             /* display last move only if game was not loaded from file */
11425             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11426                 DisplayMove(currentMove - 1);
11427
11428             if (forwardMostMove != 0) {
11429                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11430                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11431                                                                 ) {
11432                     if (*appData.saveGameFile != NULLCHAR) {
11433                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11434                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11435                         else
11436                         SaveGameToFile(appData.saveGameFile, TRUE);
11437                     } else if (appData.autoSaveGames) {
11438                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11439                     }
11440                     if (*appData.savePositionFile != NULLCHAR) {
11441                         SavePositionToFile(appData.savePositionFile);
11442                     }
11443                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11444                 }
11445             }
11446
11447             /* Tell program how game ended in case it is learning */
11448             /* [HGM] Moved this to after saving the PGN, just in case */
11449             /* engine died and we got here through time loss. In that */
11450             /* case we will get a fatal error writing the pipe, which */
11451             /* would otherwise lose us the PGN.                       */
11452             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11453             /* output during GameEnds should never be fatal anymore   */
11454             if (gameMode == MachinePlaysWhite ||
11455                 gameMode == MachinePlaysBlack ||
11456                 gameMode == TwoMachinesPlay ||
11457                 gameMode == IcsPlayingWhite ||
11458                 gameMode == IcsPlayingBlack ||
11459                 gameMode == BeginningOfGame) {
11460                 char buf[MSG_SIZ];
11461                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11462                         resultDetails);
11463                 if (first.pr != NoProc) {
11464                     SendToProgram(buf, &first);
11465                 }
11466                 if (second.pr != NoProc &&
11467                     gameMode == TwoMachinesPlay) {
11468                     SendToProgram(buf, &second);
11469                 }
11470             }
11471         }
11472
11473         if (appData.icsActive) {
11474             if (appData.quietPlay &&
11475                 (gameMode == IcsPlayingWhite ||
11476                  gameMode == IcsPlayingBlack)) {
11477                 SendToICS(ics_prefix);
11478                 SendToICS("set shout 1\n");
11479             }
11480             nextGameMode = IcsIdle;
11481             ics_user_moved = FALSE;
11482             /* clean up premove.  It's ugly when the game has ended and the
11483              * premove highlights are still on the board.
11484              */
11485             if (gotPremove) {
11486               gotPremove = FALSE;
11487               ClearPremoveHighlights();
11488               DrawPosition(FALSE, boards[currentMove]);
11489             }
11490             if (whosays == GE_ICS) {
11491                 switch (result) {
11492                 case WhiteWins:
11493                     if (gameMode == IcsPlayingWhite)
11494                         PlayIcsWinSound();
11495                     else if(gameMode == IcsPlayingBlack)
11496                         PlayIcsLossSound();
11497                     break;
11498                 case BlackWins:
11499                     if (gameMode == IcsPlayingBlack)
11500                         PlayIcsWinSound();
11501                     else if(gameMode == IcsPlayingWhite)
11502                         PlayIcsLossSound();
11503                     break;
11504                 case GameIsDrawn:
11505                     PlayIcsDrawSound();
11506                     break;
11507                 default:
11508                     PlayIcsUnfinishedSound();
11509                 }
11510             }
11511             if(appData.quitNext) { ExitEvent(0); return; }
11512         } else if (gameMode == EditGame ||
11513                    gameMode == PlayFromGameFile ||
11514                    gameMode == AnalyzeMode ||
11515                    gameMode == AnalyzeFile) {
11516             nextGameMode = gameMode;
11517         } else {
11518             nextGameMode = EndOfGame;
11519         }
11520         pausing = FALSE;
11521         ModeHighlight();
11522     } else {
11523         nextGameMode = gameMode;
11524     }
11525
11526     if (appData.noChessProgram) {
11527         gameMode = nextGameMode;
11528         ModeHighlight();
11529         endingGame = 0; /* [HGM] crash */
11530         return;
11531     }
11532
11533     if (first.reuse) {
11534         /* Put first chess program into idle state */
11535         if (first.pr != NoProc &&
11536             (gameMode == MachinePlaysWhite ||
11537              gameMode == MachinePlaysBlack ||
11538              gameMode == TwoMachinesPlay ||
11539              gameMode == IcsPlayingWhite ||
11540              gameMode == IcsPlayingBlack ||
11541              gameMode == BeginningOfGame)) {
11542             SendToProgram("force\n", &first);
11543             if (first.usePing) {
11544               char buf[MSG_SIZ];
11545               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11546               SendToProgram(buf, &first);
11547             }
11548         }
11549     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11550         /* Kill off first chess program */
11551         if (first.isr != NULL)
11552           RemoveInputSource(first.isr);
11553         first.isr = NULL;
11554
11555         if (first.pr != NoProc) {
11556             ExitAnalyzeMode();
11557             DoSleep( appData.delayBeforeQuit );
11558             SendToProgram("quit\n", &first);
11559             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11560             first.reload = TRUE;
11561         }
11562         first.pr = NoProc;
11563     }
11564     if (second.reuse) {
11565         /* Put second chess program into idle state */
11566         if (second.pr != NoProc &&
11567             gameMode == TwoMachinesPlay) {
11568             SendToProgram("force\n", &second);
11569             if (second.usePing) {
11570               char buf[MSG_SIZ];
11571               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11572               SendToProgram(buf, &second);
11573             }
11574         }
11575     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11576         /* Kill off second chess program */
11577         if (second.isr != NULL)
11578           RemoveInputSource(second.isr);
11579         second.isr = NULL;
11580
11581         if (second.pr != NoProc) {
11582             DoSleep( appData.delayBeforeQuit );
11583             SendToProgram("quit\n", &second);
11584             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11585             second.reload = TRUE;
11586         }
11587         second.pr = NoProc;
11588     }
11589
11590     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11591         char resChar = '=';
11592         switch (result) {
11593         case WhiteWins:
11594           resChar = '+';
11595           if (first.twoMachinesColor[0] == 'w') {
11596             first.matchWins++;
11597           } else {
11598             second.matchWins++;
11599           }
11600           break;
11601         case BlackWins:
11602           resChar = '-';
11603           if (first.twoMachinesColor[0] == 'b') {
11604             first.matchWins++;
11605           } else {
11606             second.matchWins++;
11607           }
11608           break;
11609         case GameUnfinished:
11610           resChar = ' ';
11611         default:
11612           break;
11613         }
11614
11615         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11616         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11617             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11618             ReserveGame(nextGame, resChar); // sets nextGame
11619             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11620             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11621         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11622
11623         if (nextGame <= appData.matchGames && !abortMatch) {
11624             gameMode = nextGameMode;
11625             matchGame = nextGame; // this will be overruled in tourney mode!
11626             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11627             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11628             endingGame = 0; /* [HGM] crash */
11629             return;
11630         } else {
11631             gameMode = nextGameMode;
11632             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11633                      first.tidy, second.tidy,
11634                      first.matchWins, second.matchWins,
11635                      appData.matchGames - (first.matchWins + second.matchWins));
11636             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11637             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11638             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11639             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11640                 first.twoMachinesColor = "black\n";
11641                 second.twoMachinesColor = "white\n";
11642             } else {
11643                 first.twoMachinesColor = "white\n";
11644                 second.twoMachinesColor = "black\n";
11645             }
11646         }
11647     }
11648     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11649         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11650       ExitAnalyzeMode();
11651     gameMode = nextGameMode;
11652     ModeHighlight();
11653     endingGame = 0;  /* [HGM] crash */
11654     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11655         if(matchMode == TRUE) { // match through command line: exit with or without popup
11656             if(ranking) {
11657                 ToNrEvent(forwardMostMove);
11658                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11659                 else ExitEvent(0);
11660             } else DisplayFatalError(buf, 0, 0);
11661         } else { // match through menu; just stop, with or without popup
11662             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11663             ModeHighlight();
11664             if(ranking){
11665                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11666             } else DisplayNote(buf);
11667       }
11668       if(ranking) free(ranking);
11669     }
11670 }
11671
11672 /* Assumes program was just initialized (initString sent).
11673    Leaves program in force mode. */
11674 void
11675 FeedMovesToProgram (ChessProgramState *cps, int upto)
11676 {
11677     int i;
11678
11679     if (appData.debugMode)
11680       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11681               startedFromSetupPosition ? "position and " : "",
11682               backwardMostMove, upto, cps->which);
11683     if(currentlyInitializedVariant != gameInfo.variant) {
11684       char buf[MSG_SIZ];
11685         // [HGM] variantswitch: make engine aware of new variant
11686         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11687                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11688                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11689         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11690         SendToProgram(buf, cps);
11691         currentlyInitializedVariant = gameInfo.variant;
11692     }
11693     SendToProgram("force\n", cps);
11694     if (startedFromSetupPosition) {
11695         SendBoard(cps, backwardMostMove);
11696     if (appData.debugMode) {
11697         fprintf(debugFP, "feedMoves\n");
11698     }
11699     }
11700     for (i = backwardMostMove; i < upto; i++) {
11701         SendMoveToProgram(i, cps);
11702     }
11703 }
11704
11705
11706 int
11707 ResurrectChessProgram ()
11708 {
11709      /* The chess program may have exited.
11710         If so, restart it and feed it all the moves made so far. */
11711     static int doInit = 0;
11712
11713     if (appData.noChessProgram) return 1;
11714
11715     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11716         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11717         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11718         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11719     } else {
11720         if (first.pr != NoProc) return 1;
11721         StartChessProgram(&first);
11722     }
11723     InitChessProgram(&first, FALSE);
11724     FeedMovesToProgram(&first, currentMove);
11725
11726     if (!first.sendTime) {
11727         /* can't tell gnuchess what its clock should read,
11728            so we bow to its notion. */
11729         ResetClocks();
11730         timeRemaining[0][currentMove] = whiteTimeRemaining;
11731         timeRemaining[1][currentMove] = blackTimeRemaining;
11732     }
11733
11734     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11735                 appData.icsEngineAnalyze) && first.analysisSupport) {
11736       SendToProgram("analyze\n", &first);
11737       first.analyzing = TRUE;
11738     }
11739     return 1;
11740 }
11741
11742 /*
11743  * Button procedures
11744  */
11745 void
11746 Reset (int redraw, int init)
11747 {
11748     int i;
11749
11750     if (appData.debugMode) {
11751         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11752                 redraw, init, gameMode);
11753     }
11754     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11755     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11756     CleanupTail(); // [HGM] vari: delete any stored variations
11757     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11758     pausing = pauseExamInvalid = FALSE;
11759     startedFromSetupPosition = blackPlaysFirst = FALSE;
11760     firstMove = TRUE;
11761     whiteFlag = blackFlag = FALSE;
11762     userOfferedDraw = FALSE;
11763     hintRequested = bookRequested = FALSE;
11764     first.maybeThinking = FALSE;
11765     second.maybeThinking = FALSE;
11766     first.bookSuspend = FALSE; // [HGM] book
11767     second.bookSuspend = FALSE;
11768     thinkOutput[0] = NULLCHAR;
11769     lastHint[0] = NULLCHAR;
11770     ClearGameInfo(&gameInfo);
11771     gameInfo.variant = StringToVariant(appData.variant);
11772     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11773     ics_user_moved = ics_clock_paused = FALSE;
11774     ics_getting_history = H_FALSE;
11775     ics_gamenum = -1;
11776     white_holding[0] = black_holding[0] = NULLCHAR;
11777     ClearProgramStats();
11778     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11779
11780     ResetFrontEnd();
11781     ClearHighlights();
11782     flipView = appData.flipView;
11783     ClearPremoveHighlights();
11784     gotPremove = FALSE;
11785     alarmSounded = FALSE;
11786     killX = killY = -1; // [HGM] lion
11787
11788     GameEnds(EndOfFile, NULL, GE_PLAYER);
11789     if(appData.serverMovesName != NULL) {
11790         /* [HGM] prepare to make moves file for broadcasting */
11791         clock_t t = clock();
11792         if(serverMoves != NULL) fclose(serverMoves);
11793         serverMoves = fopen(appData.serverMovesName, "r");
11794         if(serverMoves != NULL) {
11795             fclose(serverMoves);
11796             /* delay 15 sec before overwriting, so all clients can see end */
11797             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11798         }
11799         serverMoves = fopen(appData.serverMovesName, "w");
11800     }
11801
11802     ExitAnalyzeMode();
11803     gameMode = BeginningOfGame;
11804     ModeHighlight();
11805     if(appData.icsActive) gameInfo.variant = VariantNormal;
11806     currentMove = forwardMostMove = backwardMostMove = 0;
11807     MarkTargetSquares(1);
11808     InitPosition(redraw);
11809     for (i = 0; i < MAX_MOVES; i++) {
11810         if (commentList[i] != NULL) {
11811             free(commentList[i]);
11812             commentList[i] = NULL;
11813         }
11814     }
11815     ResetClocks();
11816     timeRemaining[0][0] = whiteTimeRemaining;
11817     timeRemaining[1][0] = blackTimeRemaining;
11818
11819     if (first.pr == NoProc) {
11820         StartChessProgram(&first);
11821     }
11822     if (init) {
11823             InitChessProgram(&first, startedFromSetupPosition);
11824     }
11825     DisplayTitle("");
11826     DisplayMessage("", "");
11827     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11828     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11829     ClearMap();        // [HGM] exclude: invalidate map
11830 }
11831
11832 void
11833 AutoPlayGameLoop ()
11834 {
11835     for (;;) {
11836         if (!AutoPlayOneMove())
11837           return;
11838         if (matchMode || appData.timeDelay == 0)
11839           continue;
11840         if (appData.timeDelay < 0)
11841           return;
11842         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11843         break;
11844     }
11845 }
11846
11847 void
11848 AnalyzeNextGame()
11849 {
11850     ReloadGame(1); // next game
11851 }
11852
11853 int
11854 AutoPlayOneMove ()
11855 {
11856     int fromX, fromY, toX, toY;
11857
11858     if (appData.debugMode) {
11859       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11860     }
11861
11862     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11863       return FALSE;
11864
11865     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11866       pvInfoList[currentMove].depth = programStats.depth;
11867       pvInfoList[currentMove].score = programStats.score;
11868       pvInfoList[currentMove].time  = 0;
11869       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11870       else { // append analysis of final position as comment
11871         char buf[MSG_SIZ];
11872         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11873         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11874       }
11875       programStats.depth = 0;
11876     }
11877
11878     if (currentMove >= forwardMostMove) {
11879       if(gameMode == AnalyzeFile) {
11880           if(appData.loadGameIndex == -1) {
11881             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11882           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11883           } else {
11884           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11885         }
11886       }
11887 //      gameMode = EndOfGame;
11888 //      ModeHighlight();
11889
11890       /* [AS] Clear current move marker at the end of a game */
11891       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11892
11893       return FALSE;
11894     }
11895
11896     toX = moveList[currentMove][2] - AAA;
11897     toY = moveList[currentMove][3] - ONE;
11898
11899     if (moveList[currentMove][1] == '@') {
11900         if (appData.highlightLastMove) {
11901             SetHighlights(-1, -1, toX, toY);
11902         }
11903     } else {
11904         int viaX = moveList[currentMove][5] - AAA;
11905         int viaY = moveList[currentMove][6] - ONE;
11906         fromX = moveList[currentMove][0] - AAA;
11907         fromY = moveList[currentMove][1] - ONE;
11908
11909         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11910
11911         if(moveList[currentMove][4] == ';') { // multi-leg
11912             ChessSquare piece = boards[currentMove][viaY][viaX];
11913             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11914             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11915             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11916             boards[currentMove][viaY][viaX] = piece;
11917         } else
11918         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11919
11920         if (appData.highlightLastMove) {
11921             SetHighlights(fromX, fromY, toX, toY);
11922         }
11923     }
11924     DisplayMove(currentMove);
11925     SendMoveToProgram(currentMove++, &first);
11926     DisplayBothClocks();
11927     DrawPosition(FALSE, boards[currentMove]);
11928     // [HGM] PV info: always display, routine tests if empty
11929     DisplayComment(currentMove - 1, commentList[currentMove]);
11930     return TRUE;
11931 }
11932
11933
11934 int
11935 LoadGameOneMove (ChessMove readAhead)
11936 {
11937     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11938     char promoChar = NULLCHAR;
11939     ChessMove moveType;
11940     char move[MSG_SIZ];
11941     char *p, *q;
11942
11943     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11944         gameMode != AnalyzeMode && gameMode != Training) {
11945         gameFileFP = NULL;
11946         return FALSE;
11947     }
11948
11949     yyboardindex = forwardMostMove;
11950     if (readAhead != EndOfFile) {
11951       moveType = readAhead;
11952     } else {
11953       if (gameFileFP == NULL)
11954           return FALSE;
11955       moveType = (ChessMove) Myylex();
11956     }
11957
11958     done = FALSE;
11959     switch (moveType) {
11960       case Comment:
11961         if (appData.debugMode)
11962           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11963         p = yy_text;
11964
11965         /* append the comment but don't display it */
11966         AppendComment(currentMove, p, FALSE);
11967         return TRUE;
11968
11969       case WhiteCapturesEnPassant:
11970       case BlackCapturesEnPassant:
11971       case WhitePromotion:
11972       case BlackPromotion:
11973       case WhiteNonPromotion:
11974       case BlackNonPromotion:
11975       case NormalMove:
11976       case FirstLeg:
11977       case WhiteKingSideCastle:
11978       case WhiteQueenSideCastle:
11979       case BlackKingSideCastle:
11980       case BlackQueenSideCastle:
11981       case WhiteKingSideCastleWild:
11982       case WhiteQueenSideCastleWild:
11983       case BlackKingSideCastleWild:
11984       case BlackQueenSideCastleWild:
11985       /* PUSH Fabien */
11986       case WhiteHSideCastleFR:
11987       case WhiteASideCastleFR:
11988       case BlackHSideCastleFR:
11989       case BlackASideCastleFR:
11990       /* POP Fabien */
11991         if (appData.debugMode)
11992           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11993         fromX = currentMoveString[0] - AAA;
11994         fromY = currentMoveString[1] - ONE;
11995         toX = currentMoveString[2] - AAA;
11996         toY = currentMoveString[3] - ONE;
11997         promoChar = currentMoveString[4];
11998         if(promoChar == ';') promoChar = NULLCHAR;
11999         break;
12000
12001       case WhiteDrop:
12002       case BlackDrop:
12003         if (appData.debugMode)
12004           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12005         fromX = moveType == WhiteDrop ?
12006           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12007         (int) CharToPiece(ToLower(currentMoveString[0]));
12008         fromY = DROP_RANK;
12009         toX = currentMoveString[2] - AAA;
12010         toY = currentMoveString[3] - ONE;
12011         break;
12012
12013       case WhiteWins:
12014       case BlackWins:
12015       case GameIsDrawn:
12016       case GameUnfinished:
12017         if (appData.debugMode)
12018           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12019         p = strchr(yy_text, '{');
12020         if (p == NULL) p = strchr(yy_text, '(');
12021         if (p == NULL) {
12022             p = yy_text;
12023             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12024         } else {
12025             q = strchr(p, *p == '{' ? '}' : ')');
12026             if (q != NULL) *q = NULLCHAR;
12027             p++;
12028         }
12029         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12030         GameEnds(moveType, p, GE_FILE);
12031         done = TRUE;
12032         if (cmailMsgLoaded) {
12033             ClearHighlights();
12034             flipView = WhiteOnMove(currentMove);
12035             if (moveType == GameUnfinished) flipView = !flipView;
12036             if (appData.debugMode)
12037               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12038         }
12039         break;
12040
12041       case EndOfFile:
12042         if (appData.debugMode)
12043           fprintf(debugFP, "Parser hit end of file\n");
12044         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12045           case MT_NONE:
12046           case MT_CHECK:
12047             break;
12048           case MT_CHECKMATE:
12049           case MT_STAINMATE:
12050             if (WhiteOnMove(currentMove)) {
12051                 GameEnds(BlackWins, "Black mates", GE_FILE);
12052             } else {
12053                 GameEnds(WhiteWins, "White mates", GE_FILE);
12054             }
12055             break;
12056           case MT_STALEMATE:
12057             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12058             break;
12059         }
12060         done = TRUE;
12061         break;
12062
12063       case MoveNumberOne:
12064         if (lastLoadGameStart == GNUChessGame) {
12065             /* GNUChessGames have numbers, but they aren't move numbers */
12066             if (appData.debugMode)
12067               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12068                       yy_text, (int) moveType);
12069             return LoadGameOneMove(EndOfFile); /* tail recursion */
12070         }
12071         /* else fall thru */
12072
12073       case XBoardGame:
12074       case GNUChessGame:
12075       case PGNTag:
12076         /* Reached start of next game in file */
12077         if (appData.debugMode)
12078           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12079         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12080           case MT_NONE:
12081           case MT_CHECK:
12082             break;
12083           case MT_CHECKMATE:
12084           case MT_STAINMATE:
12085             if (WhiteOnMove(currentMove)) {
12086                 GameEnds(BlackWins, "Black mates", GE_FILE);
12087             } else {
12088                 GameEnds(WhiteWins, "White mates", GE_FILE);
12089             }
12090             break;
12091           case MT_STALEMATE:
12092             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12093             break;
12094         }
12095         done = TRUE;
12096         break;
12097
12098       case PositionDiagram:     /* should not happen; ignore */
12099       case ElapsedTime:         /* ignore */
12100       case NAG:                 /* ignore */
12101         if (appData.debugMode)
12102           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12103                   yy_text, (int) moveType);
12104         return LoadGameOneMove(EndOfFile); /* tail recursion */
12105
12106       case IllegalMove:
12107         if (appData.testLegality) {
12108             if (appData.debugMode)
12109               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12110             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12111                     (forwardMostMove / 2) + 1,
12112                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12113             DisplayError(move, 0);
12114             done = TRUE;
12115         } else {
12116             if (appData.debugMode)
12117               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12118                       yy_text, currentMoveString);
12119             if(currentMoveString[1] == '@') {
12120                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12121                 fromY = DROP_RANK;
12122             } else {
12123                 fromX = currentMoveString[0] - AAA;
12124                 fromY = currentMoveString[1] - ONE;
12125             }
12126             toX = currentMoveString[2] - AAA;
12127             toY = currentMoveString[3] - ONE;
12128             promoChar = currentMoveString[4];
12129         }
12130         break;
12131
12132       case AmbiguousMove:
12133         if (appData.debugMode)
12134           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12135         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12136                 (forwardMostMove / 2) + 1,
12137                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12138         DisplayError(move, 0);
12139         done = TRUE;
12140         break;
12141
12142       default:
12143       case ImpossibleMove:
12144         if (appData.debugMode)
12145           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12146         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12147                 (forwardMostMove / 2) + 1,
12148                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12149         DisplayError(move, 0);
12150         done = TRUE;
12151         break;
12152     }
12153
12154     if (done) {
12155         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12156             DrawPosition(FALSE, boards[currentMove]);
12157             DisplayBothClocks();
12158             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12159               DisplayComment(currentMove - 1, commentList[currentMove]);
12160         }
12161         (void) StopLoadGameTimer();
12162         gameFileFP = NULL;
12163         cmailOldMove = forwardMostMove;
12164         return FALSE;
12165     } else {
12166         /* currentMoveString is set as a side-effect of yylex */
12167
12168         thinkOutput[0] = NULLCHAR;
12169         MakeMove(fromX, fromY, toX, toY, promoChar);
12170         killX = killY = -1; // [HGM] lion: used up
12171         currentMove = forwardMostMove;
12172         return TRUE;
12173     }
12174 }
12175
12176 /* Load the nth game from the given file */
12177 int
12178 LoadGameFromFile (char *filename, int n, char *title, int useList)
12179 {
12180     FILE *f;
12181     char buf[MSG_SIZ];
12182
12183     if (strcmp(filename, "-") == 0) {
12184         f = stdin;
12185         title = "stdin";
12186     } else {
12187         f = fopen(filename, "rb");
12188         if (f == NULL) {
12189           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12190             DisplayError(buf, errno);
12191             return FALSE;
12192         }
12193     }
12194     if (fseek(f, 0, 0) == -1) {
12195         /* f is not seekable; probably a pipe */
12196         useList = FALSE;
12197     }
12198     if (useList && n == 0) {
12199         int error = GameListBuild(f);
12200         if (error) {
12201             DisplayError(_("Cannot build game list"), error);
12202         } else if (!ListEmpty(&gameList) &&
12203                    ((ListGame *) gameList.tailPred)->number > 1) {
12204             GameListPopUp(f, title);
12205             return TRUE;
12206         }
12207         GameListDestroy();
12208         n = 1;
12209     }
12210     if (n == 0) n = 1;
12211     return LoadGame(f, n, title, FALSE);
12212 }
12213
12214
12215 void
12216 MakeRegisteredMove ()
12217 {
12218     int fromX, fromY, toX, toY;
12219     char promoChar;
12220     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12221         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12222           case CMAIL_MOVE:
12223           case CMAIL_DRAW:
12224             if (appData.debugMode)
12225               fprintf(debugFP, "Restoring %s for game %d\n",
12226                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12227
12228             thinkOutput[0] = NULLCHAR;
12229             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12230             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12231             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12232             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12233             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12234             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12235             MakeMove(fromX, fromY, toX, toY, promoChar);
12236             ShowMove(fromX, fromY, toX, toY);
12237
12238             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12239               case MT_NONE:
12240               case MT_CHECK:
12241                 break;
12242
12243               case MT_CHECKMATE:
12244               case MT_STAINMATE:
12245                 if (WhiteOnMove(currentMove)) {
12246                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12247                 } else {
12248                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12249                 }
12250                 break;
12251
12252               case MT_STALEMATE:
12253                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12254                 break;
12255             }
12256
12257             break;
12258
12259           case CMAIL_RESIGN:
12260             if (WhiteOnMove(currentMove)) {
12261                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12262             } else {
12263                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12264             }
12265             break;
12266
12267           case CMAIL_ACCEPT:
12268             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12269             break;
12270
12271           default:
12272             break;
12273         }
12274     }
12275
12276     return;
12277 }
12278
12279 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12280 int
12281 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12282 {
12283     int retVal;
12284
12285     if (gameNumber > nCmailGames) {
12286         DisplayError(_("No more games in this message"), 0);
12287         return FALSE;
12288     }
12289     if (f == lastLoadGameFP) {
12290         int offset = gameNumber - lastLoadGameNumber;
12291         if (offset == 0) {
12292             cmailMsg[0] = NULLCHAR;
12293             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12294                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12295                 nCmailMovesRegistered--;
12296             }
12297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12298             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12299                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12300             }
12301         } else {
12302             if (! RegisterMove()) return FALSE;
12303         }
12304     }
12305
12306     retVal = LoadGame(f, gameNumber, title, useList);
12307
12308     /* Make move registered during previous look at this game, if any */
12309     MakeRegisteredMove();
12310
12311     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12312         commentList[currentMove]
12313           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12314         DisplayComment(currentMove - 1, commentList[currentMove]);
12315     }
12316
12317     return retVal;
12318 }
12319
12320 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12321 int
12322 ReloadGame (int offset)
12323 {
12324     int gameNumber = lastLoadGameNumber + offset;
12325     if (lastLoadGameFP == NULL) {
12326         DisplayError(_("No game has been loaded yet"), 0);
12327         return FALSE;
12328     }
12329     if (gameNumber <= 0) {
12330         DisplayError(_("Can't back up any further"), 0);
12331         return FALSE;
12332     }
12333     if (cmailMsgLoaded) {
12334         return CmailLoadGame(lastLoadGameFP, gameNumber,
12335                              lastLoadGameTitle, lastLoadGameUseList);
12336     } else {
12337         return LoadGame(lastLoadGameFP, gameNumber,
12338                         lastLoadGameTitle, lastLoadGameUseList);
12339     }
12340 }
12341
12342 int keys[EmptySquare+1];
12343
12344 int
12345 PositionMatches (Board b1, Board b2)
12346 {
12347     int r, f, sum=0;
12348     switch(appData.searchMode) {
12349         case 1: return CompareWithRights(b1, b2);
12350         case 2:
12351             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12352                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12353             }
12354             return TRUE;
12355         case 3:
12356             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12357               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12358                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12359             }
12360             return sum==0;
12361         case 4:
12362             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12363                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12364             }
12365             return sum==0;
12366     }
12367     return TRUE;
12368 }
12369
12370 #define Q_PROMO  4
12371 #define Q_EP     3
12372 #define Q_BCASTL 2
12373 #define Q_WCASTL 1
12374
12375 int pieceList[256], quickBoard[256];
12376 ChessSquare pieceType[256] = { EmptySquare };
12377 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12378 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12379 int soughtTotal, turn;
12380 Boolean epOK, flipSearch;
12381
12382 typedef struct {
12383     unsigned char piece, to;
12384 } Move;
12385
12386 #define DSIZE (250000)
12387
12388 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12389 Move *moveDatabase = initialSpace;
12390 unsigned int movePtr, dataSize = DSIZE;
12391
12392 int
12393 MakePieceList (Board board, int *counts)
12394 {
12395     int r, f, n=Q_PROMO, total=0;
12396     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12397     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12398         int sq = f + (r<<4);
12399         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12400             quickBoard[sq] = ++n;
12401             pieceList[n] = sq;
12402             pieceType[n] = board[r][f];
12403             counts[board[r][f]]++;
12404             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12405             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12406             total++;
12407         }
12408     }
12409     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12410     return total;
12411 }
12412
12413 void
12414 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12415 {
12416     int sq = fromX + (fromY<<4);
12417     int piece = quickBoard[sq], rook;
12418     quickBoard[sq] = 0;
12419     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12420     if(piece == pieceList[1] && fromY == toY) {
12421       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12422         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12423         moveDatabase[movePtr++].piece = Q_WCASTL;
12424         quickBoard[sq] = piece;
12425         piece = quickBoard[from]; quickBoard[from] = 0;
12426         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12427       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12428         quickBoard[sq] = 0; // remove Rook
12429         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12430         moveDatabase[movePtr++].piece = Q_WCASTL;
12431         quickBoard[sq] = pieceList[1]; // put King
12432         piece = rook;
12433         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12434       }
12435     } else
12436     if(piece == pieceList[2] && fromY == toY) {
12437       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12438         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12439         moveDatabase[movePtr++].piece = Q_BCASTL;
12440         quickBoard[sq] = piece;
12441         piece = quickBoard[from]; quickBoard[from] = 0;
12442         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12443       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12444         quickBoard[sq] = 0; // remove Rook
12445         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12446         moveDatabase[movePtr++].piece = Q_BCASTL;
12447         quickBoard[sq] = pieceList[2]; // put King
12448         piece = rook;
12449         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12450       }
12451     } else
12452     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12453         quickBoard[(fromY<<4)+toX] = 0;
12454         moveDatabase[movePtr].piece = Q_EP;
12455         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12456         moveDatabase[movePtr].to = sq;
12457     } else
12458     if(promoPiece != pieceType[piece]) {
12459         moveDatabase[movePtr++].piece = Q_PROMO;
12460         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12461     }
12462     moveDatabase[movePtr].piece = piece;
12463     quickBoard[sq] = piece;
12464     movePtr++;
12465 }
12466
12467 int
12468 PackGame (Board board)
12469 {
12470     Move *newSpace = NULL;
12471     moveDatabase[movePtr].piece = 0; // terminate previous game
12472     if(movePtr > dataSize) {
12473         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12474         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12475         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12476         if(newSpace) {
12477             int i;
12478             Move *p = moveDatabase, *q = newSpace;
12479             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12480             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12481             moveDatabase = newSpace;
12482         } else { // calloc failed, we must be out of memory. Too bad...
12483             dataSize = 0; // prevent calloc events for all subsequent games
12484             return 0;     // and signal this one isn't cached
12485         }
12486     }
12487     movePtr++;
12488     MakePieceList(board, counts);
12489     return movePtr;
12490 }
12491
12492 int
12493 QuickCompare (Board board, int *minCounts, int *maxCounts)
12494 {   // compare according to search mode
12495     int r, f;
12496     switch(appData.searchMode)
12497     {
12498       case 1: // exact position match
12499         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12500         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12501             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12502         }
12503         break;
12504       case 2: // can have extra material on empty squares
12505         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12506             if(board[r][f] == EmptySquare) continue;
12507             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12508         }
12509         break;
12510       case 3: // material with exact Pawn structure
12511         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12512             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12513             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12514         } // fall through to material comparison
12515       case 4: // exact material
12516         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12517         break;
12518       case 6: // material range with given imbalance
12519         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12520         // fall through to range comparison
12521       case 5: // material range
12522         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12523     }
12524     return TRUE;
12525 }
12526
12527 int
12528 QuickScan (Board board, Move *move)
12529 {   // reconstruct game,and compare all positions in it
12530     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12531     do {
12532         int piece = move->piece;
12533         int to = move->to, from = pieceList[piece];
12534         if(found < 0) { // if already found just scan to game end for final piece count
12535           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12536            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12537            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12538                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12539             ) {
12540             static int lastCounts[EmptySquare+1];
12541             int i;
12542             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12543             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12544           } else stretch = 0;
12545           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12546           if(found >= 0 && !appData.minPieces) return found;
12547         }
12548         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12549           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12550           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12551             piece = (++move)->piece;
12552             from = pieceList[piece];
12553             counts[pieceType[piece]]--;
12554             pieceType[piece] = (ChessSquare) move->to;
12555             counts[move->to]++;
12556           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12557             counts[pieceType[quickBoard[to]]]--;
12558             quickBoard[to] = 0; total--;
12559             move++;
12560             continue;
12561           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12562             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12563             from  = pieceList[piece]; // so this must be King
12564             quickBoard[from] = 0;
12565             pieceList[piece] = to;
12566             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12567             quickBoard[from] = 0; // rook
12568             quickBoard[to] = piece;
12569             to = move->to; piece = move->piece;
12570             goto aftercastle;
12571           }
12572         }
12573         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12574         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12575         quickBoard[from] = 0;
12576       aftercastle:
12577         quickBoard[to] = piece;
12578         pieceList[piece] = to;
12579         cnt++; turn ^= 3;
12580         move++;
12581     } while(1);
12582 }
12583
12584 void
12585 InitSearch ()
12586 {
12587     int r, f;
12588     flipSearch = FALSE;
12589     CopyBoard(soughtBoard, boards[currentMove]);
12590     soughtTotal = MakePieceList(soughtBoard, maxSought);
12591     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12592     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12593     CopyBoard(reverseBoard, boards[currentMove]);
12594     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12595         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12596         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12597         reverseBoard[r][f] = piece;
12598     }
12599     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12600     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12601     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12602                  || (boards[currentMove][CASTLING][2] == NoRights ||
12603                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12604                  && (boards[currentMove][CASTLING][5] == NoRights ||
12605                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12606       ) {
12607         flipSearch = TRUE;
12608         CopyBoard(flipBoard, soughtBoard);
12609         CopyBoard(rotateBoard, reverseBoard);
12610         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12611             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12612             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12613         }
12614     }
12615     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12616     if(appData.searchMode >= 5) {
12617         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12618         MakePieceList(soughtBoard, minSought);
12619         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12620     }
12621     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12622         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12623 }
12624
12625 GameInfo dummyInfo;
12626 static int creatingBook;
12627
12628 int
12629 GameContainsPosition (FILE *f, ListGame *lg)
12630 {
12631     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12632     int fromX, fromY, toX, toY;
12633     char promoChar;
12634     static int initDone=FALSE;
12635
12636     // weed out games based on numerical tag comparison
12637     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12638     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12639     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12640     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12641     if(!initDone) {
12642         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12643         initDone = TRUE;
12644     }
12645     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12646     else CopyBoard(boards[scratch], initialPosition); // default start position
12647     if(lg->moves) {
12648         turn = btm + 1;
12649         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12650         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12651     }
12652     if(btm) plyNr++;
12653     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12654     fseek(f, lg->offset, 0);
12655     yynewfile(f);
12656     while(1) {
12657         yyboardindex = scratch;
12658         quickFlag = plyNr+1;
12659         next = Myylex();
12660         quickFlag = 0;
12661         switch(next) {
12662             case PGNTag:
12663                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12664             default:
12665                 continue;
12666
12667             case XBoardGame:
12668             case GNUChessGame:
12669                 if(plyNr) return -1; // after we have seen moves, this is for new game
12670               continue;
12671
12672             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12673             case ImpossibleMove:
12674             case WhiteWins: // game ends here with these four
12675             case BlackWins:
12676             case GameIsDrawn:
12677             case GameUnfinished:
12678                 return -1;
12679
12680             case IllegalMove:
12681                 if(appData.testLegality) return -1;
12682             case WhiteCapturesEnPassant:
12683             case BlackCapturesEnPassant:
12684             case WhitePromotion:
12685             case BlackPromotion:
12686             case WhiteNonPromotion:
12687             case BlackNonPromotion:
12688             case NormalMove:
12689             case FirstLeg:
12690             case WhiteKingSideCastle:
12691             case WhiteQueenSideCastle:
12692             case BlackKingSideCastle:
12693             case BlackQueenSideCastle:
12694             case WhiteKingSideCastleWild:
12695             case WhiteQueenSideCastleWild:
12696             case BlackKingSideCastleWild:
12697             case BlackQueenSideCastleWild:
12698             case WhiteHSideCastleFR:
12699             case WhiteASideCastleFR:
12700             case BlackHSideCastleFR:
12701             case BlackASideCastleFR:
12702                 fromX = currentMoveString[0] - AAA;
12703                 fromY = currentMoveString[1] - ONE;
12704                 toX = currentMoveString[2] - AAA;
12705                 toY = currentMoveString[3] - ONE;
12706                 promoChar = currentMoveString[4];
12707                 break;
12708             case WhiteDrop:
12709             case BlackDrop:
12710                 fromX = next == WhiteDrop ?
12711                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12712                   (int) CharToPiece(ToLower(currentMoveString[0]));
12713                 fromY = DROP_RANK;
12714                 toX = currentMoveString[2] - AAA;
12715                 toY = currentMoveString[3] - ONE;
12716                 promoChar = 0;
12717                 break;
12718         }
12719         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12720         plyNr++;
12721         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12722         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12723         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12724         if(appData.findMirror) {
12725             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12726             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12727         }
12728     }
12729 }
12730
12731 /* Load the nth game from open file f */
12732 int
12733 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12734 {
12735     ChessMove cm;
12736     char buf[MSG_SIZ];
12737     int gn = gameNumber;
12738     ListGame *lg = NULL;
12739     int numPGNTags = 0;
12740     int err, pos = -1;
12741     GameMode oldGameMode;
12742     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12743     char oldName[MSG_SIZ];
12744
12745     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12746
12747     if (appData.debugMode)
12748         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12749
12750     if (gameMode == Training )
12751         SetTrainingModeOff();
12752
12753     oldGameMode = gameMode;
12754     if (gameMode != BeginningOfGame) {
12755       Reset(FALSE, TRUE);
12756     }
12757     killX = killY = -1; // [HGM] lion: in case we did not Reset
12758
12759     gameFileFP = f;
12760     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12761         fclose(lastLoadGameFP);
12762     }
12763
12764     if (useList) {
12765         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12766
12767         if (lg) {
12768             fseek(f, lg->offset, 0);
12769             GameListHighlight(gameNumber);
12770             pos = lg->position;
12771             gn = 1;
12772         }
12773         else {
12774             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12775               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12776             else
12777             DisplayError(_("Game number out of range"), 0);
12778             return FALSE;
12779         }
12780     } else {
12781         GameListDestroy();
12782         if (fseek(f, 0, 0) == -1) {
12783             if (f == lastLoadGameFP ?
12784                 gameNumber == lastLoadGameNumber + 1 :
12785                 gameNumber == 1) {
12786                 gn = 1;
12787             } else {
12788                 DisplayError(_("Can't seek on game file"), 0);
12789                 return FALSE;
12790             }
12791         }
12792     }
12793     lastLoadGameFP = f;
12794     lastLoadGameNumber = gameNumber;
12795     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12796     lastLoadGameUseList = useList;
12797
12798     yynewfile(f);
12799
12800     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12801       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12802                 lg->gameInfo.black);
12803             DisplayTitle(buf);
12804     } else if (*title != NULLCHAR) {
12805         if (gameNumber > 1) {
12806           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12807             DisplayTitle(buf);
12808         } else {
12809             DisplayTitle(title);
12810         }
12811     }
12812
12813     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12814         gameMode = PlayFromGameFile;
12815         ModeHighlight();
12816     }
12817
12818     currentMove = forwardMostMove = backwardMostMove = 0;
12819     CopyBoard(boards[0], initialPosition);
12820     StopClocks();
12821
12822     /*
12823      * Skip the first gn-1 games in the file.
12824      * Also skip over anything that precedes an identifiable
12825      * start of game marker, to avoid being confused by
12826      * garbage at the start of the file.  Currently
12827      * recognized start of game markers are the move number "1",
12828      * the pattern "gnuchess .* game", the pattern
12829      * "^[#;%] [^ ]* game file", and a PGN tag block.
12830      * A game that starts with one of the latter two patterns
12831      * will also have a move number 1, possibly
12832      * following a position diagram.
12833      * 5-4-02: Let's try being more lenient and allowing a game to
12834      * start with an unnumbered move.  Does that break anything?
12835      */
12836     cm = lastLoadGameStart = EndOfFile;
12837     while (gn > 0) {
12838         yyboardindex = forwardMostMove;
12839         cm = (ChessMove) Myylex();
12840         switch (cm) {
12841           case EndOfFile:
12842             if (cmailMsgLoaded) {
12843                 nCmailGames = CMAIL_MAX_GAMES - gn;
12844             } else {
12845                 Reset(TRUE, TRUE);
12846                 DisplayError(_("Game not found in file"), 0);
12847             }
12848             return FALSE;
12849
12850           case GNUChessGame:
12851           case XBoardGame:
12852             gn--;
12853             lastLoadGameStart = cm;
12854             break;
12855
12856           case MoveNumberOne:
12857             switch (lastLoadGameStart) {
12858               case GNUChessGame:
12859               case XBoardGame:
12860               case PGNTag:
12861                 break;
12862               case MoveNumberOne:
12863               case EndOfFile:
12864                 gn--;           /* count this game */
12865                 lastLoadGameStart = cm;
12866                 break;
12867               default:
12868                 /* impossible */
12869                 break;
12870             }
12871             break;
12872
12873           case PGNTag:
12874             switch (lastLoadGameStart) {
12875               case GNUChessGame:
12876               case PGNTag:
12877               case MoveNumberOne:
12878               case EndOfFile:
12879                 gn--;           /* count this game */
12880                 lastLoadGameStart = cm;
12881                 break;
12882               case XBoardGame:
12883                 lastLoadGameStart = cm; /* game counted already */
12884                 break;
12885               default:
12886                 /* impossible */
12887                 break;
12888             }
12889             if (gn > 0) {
12890                 do {
12891                     yyboardindex = forwardMostMove;
12892                     cm = (ChessMove) Myylex();
12893                 } while (cm == PGNTag || cm == Comment);
12894             }
12895             break;
12896
12897           case WhiteWins:
12898           case BlackWins:
12899           case GameIsDrawn:
12900             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12901                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12902                     != CMAIL_OLD_RESULT) {
12903                     nCmailResults ++ ;
12904                     cmailResult[  CMAIL_MAX_GAMES
12905                                 - gn - 1] = CMAIL_OLD_RESULT;
12906                 }
12907             }
12908             break;
12909
12910           case NormalMove:
12911           case FirstLeg:
12912             /* Only a NormalMove can be at the start of a game
12913              * without a position diagram. */
12914             if (lastLoadGameStart == EndOfFile ) {
12915               gn--;
12916               lastLoadGameStart = MoveNumberOne;
12917             }
12918             break;
12919
12920           default:
12921             break;
12922         }
12923     }
12924
12925     if (appData.debugMode)
12926       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12927
12928     if (cm == XBoardGame) {
12929         /* Skip any header junk before position diagram and/or move 1 */
12930         for (;;) {
12931             yyboardindex = forwardMostMove;
12932             cm = (ChessMove) Myylex();
12933
12934             if (cm == EndOfFile ||
12935                 cm == GNUChessGame || cm == XBoardGame) {
12936                 /* Empty game; pretend end-of-file and handle later */
12937                 cm = EndOfFile;
12938                 break;
12939             }
12940
12941             if (cm == MoveNumberOne || cm == PositionDiagram ||
12942                 cm == PGNTag || cm == Comment)
12943               break;
12944         }
12945     } else if (cm == GNUChessGame) {
12946         if (gameInfo.event != NULL) {
12947             free(gameInfo.event);
12948         }
12949         gameInfo.event = StrSave(yy_text);
12950     }
12951
12952     startedFromSetupPosition = FALSE;
12953     while (cm == PGNTag) {
12954         if (appData.debugMode)
12955           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12956         err = ParsePGNTag(yy_text, &gameInfo);
12957         if (!err) numPGNTags++;
12958
12959         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12960         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12961             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12962             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12963             InitPosition(TRUE);
12964             oldVariant = gameInfo.variant;
12965             if (appData.debugMode)
12966               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12967         }
12968
12969
12970         if (gameInfo.fen != NULL) {
12971           Board initial_position;
12972           startedFromSetupPosition = TRUE;
12973           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12974             Reset(TRUE, TRUE);
12975             DisplayError(_("Bad FEN position in file"), 0);
12976             return FALSE;
12977           }
12978           CopyBoard(boards[0], initial_position);
12979           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12980             CopyBoard(initialPosition, initial_position);
12981           if (blackPlaysFirst) {
12982             currentMove = forwardMostMove = backwardMostMove = 1;
12983             CopyBoard(boards[1], initial_position);
12984             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12985             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12986             timeRemaining[0][1] = whiteTimeRemaining;
12987             timeRemaining[1][1] = blackTimeRemaining;
12988             if (commentList[0] != NULL) {
12989               commentList[1] = commentList[0];
12990               commentList[0] = NULL;
12991             }
12992           } else {
12993             currentMove = forwardMostMove = backwardMostMove = 0;
12994           }
12995           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12996           {   int i;
12997               initialRulePlies = FENrulePlies;
12998               for( i=0; i< nrCastlingRights; i++ )
12999                   initialRights[i] = initial_position[CASTLING][i];
13000           }
13001           yyboardindex = forwardMostMove;
13002           free(gameInfo.fen);
13003           gameInfo.fen = NULL;
13004         }
13005
13006         yyboardindex = forwardMostMove;
13007         cm = (ChessMove) Myylex();
13008
13009         /* Handle comments interspersed among the tags */
13010         while (cm == Comment) {
13011             char *p;
13012             if (appData.debugMode)
13013               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13014             p = yy_text;
13015             AppendComment(currentMove, p, FALSE);
13016             yyboardindex = forwardMostMove;
13017             cm = (ChessMove) Myylex();
13018         }
13019     }
13020
13021     /* don't rely on existence of Event tag since if game was
13022      * pasted from clipboard the Event tag may not exist
13023      */
13024     if (numPGNTags > 0){
13025         char *tags;
13026         if (gameInfo.variant == VariantNormal) {
13027           VariantClass v = StringToVariant(gameInfo.event);
13028           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13029           if(v < VariantShogi) gameInfo.variant = v;
13030         }
13031         if (!matchMode) {
13032           if( appData.autoDisplayTags ) {
13033             tags = PGNTags(&gameInfo);
13034             TagsPopUp(tags, CmailMsg());
13035             free(tags);
13036           }
13037         }
13038     } else {
13039         /* Make something up, but don't display it now */
13040         SetGameInfo();
13041         TagsPopDown();
13042     }
13043
13044     if (cm == PositionDiagram) {
13045         int i, j;
13046         char *p;
13047         Board initial_position;
13048
13049         if (appData.debugMode)
13050           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13051
13052         if (!startedFromSetupPosition) {
13053             p = yy_text;
13054             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13055               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13056                 switch (*p) {
13057                   case '{':
13058                   case '[':
13059                   case '-':
13060                   case ' ':
13061                   case '\t':
13062                   case '\n':
13063                   case '\r':
13064                     break;
13065                   default:
13066                     initial_position[i][j++] = CharToPiece(*p);
13067                     break;
13068                 }
13069             while (*p == ' ' || *p == '\t' ||
13070                    *p == '\n' || *p == '\r') p++;
13071
13072             if (strncmp(p, "black", strlen("black"))==0)
13073               blackPlaysFirst = TRUE;
13074             else
13075               blackPlaysFirst = FALSE;
13076             startedFromSetupPosition = TRUE;
13077
13078             CopyBoard(boards[0], initial_position);
13079             if (blackPlaysFirst) {
13080                 currentMove = forwardMostMove = backwardMostMove = 1;
13081                 CopyBoard(boards[1], initial_position);
13082                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13083                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13084                 timeRemaining[0][1] = whiteTimeRemaining;
13085                 timeRemaining[1][1] = blackTimeRemaining;
13086                 if (commentList[0] != NULL) {
13087                     commentList[1] = commentList[0];
13088                     commentList[0] = NULL;
13089                 }
13090             } else {
13091                 currentMove = forwardMostMove = backwardMostMove = 0;
13092             }
13093         }
13094         yyboardindex = forwardMostMove;
13095         cm = (ChessMove) Myylex();
13096     }
13097
13098   if(!creatingBook) {
13099     if (first.pr == NoProc) {
13100         StartChessProgram(&first);
13101     }
13102     InitChessProgram(&first, FALSE);
13103     if(gameInfo.variant == VariantUnknown && *oldName) {
13104         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13105         gameInfo.variant = v;
13106     }
13107     SendToProgram("force\n", &first);
13108     if (startedFromSetupPosition) {
13109         SendBoard(&first, forwardMostMove);
13110     if (appData.debugMode) {
13111         fprintf(debugFP, "Load Game\n");
13112     }
13113         DisplayBothClocks();
13114     }
13115   }
13116
13117     /* [HGM] server: flag to write setup moves in broadcast file as one */
13118     loadFlag = appData.suppressLoadMoves;
13119
13120     while (cm == Comment) {
13121         char *p;
13122         if (appData.debugMode)
13123           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13124         p = yy_text;
13125         AppendComment(currentMove, p, FALSE);
13126         yyboardindex = forwardMostMove;
13127         cm = (ChessMove) Myylex();
13128     }
13129
13130     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13131         cm == WhiteWins || cm == BlackWins ||
13132         cm == GameIsDrawn || cm == GameUnfinished) {
13133         DisplayMessage("", _("No moves in game"));
13134         if (cmailMsgLoaded) {
13135             if (appData.debugMode)
13136               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13137             ClearHighlights();
13138             flipView = FALSE;
13139         }
13140         DrawPosition(FALSE, boards[currentMove]);
13141         DisplayBothClocks();
13142         gameMode = EditGame;
13143         ModeHighlight();
13144         gameFileFP = NULL;
13145         cmailOldMove = 0;
13146         return TRUE;
13147     }
13148
13149     // [HGM] PV info: routine tests if comment empty
13150     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13151         DisplayComment(currentMove - 1, commentList[currentMove]);
13152     }
13153     if (!matchMode && appData.timeDelay != 0)
13154       DrawPosition(FALSE, boards[currentMove]);
13155
13156     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13157       programStats.ok_to_send = 1;
13158     }
13159
13160     /* if the first token after the PGN tags is a move
13161      * and not move number 1, retrieve it from the parser
13162      */
13163     if (cm != MoveNumberOne)
13164         LoadGameOneMove(cm);
13165
13166     /* load the remaining moves from the file */
13167     while (LoadGameOneMove(EndOfFile)) {
13168       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13169       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13170     }
13171
13172     /* rewind to the start of the game */
13173     currentMove = backwardMostMove;
13174
13175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13176
13177     if (oldGameMode == AnalyzeFile) {
13178       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13179       AnalyzeFileEvent();
13180     } else
13181     if (oldGameMode == AnalyzeMode) {
13182       AnalyzeFileEvent();
13183     }
13184
13185     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13186         long int w, b; // [HGM] adjourn: restore saved clock times
13187         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13188         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13189             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13190             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13191         }
13192     }
13193
13194     if(creatingBook) return TRUE;
13195     if (!matchMode && pos > 0) {
13196         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13197     } else
13198     if (matchMode || appData.timeDelay == 0) {
13199       ToEndEvent();
13200     } else if (appData.timeDelay > 0) {
13201       AutoPlayGameLoop();
13202     }
13203
13204     if (appData.debugMode)
13205         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13206
13207     loadFlag = 0; /* [HGM] true game starts */
13208     return TRUE;
13209 }
13210
13211 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13212 int
13213 ReloadPosition (int offset)
13214 {
13215     int positionNumber = lastLoadPositionNumber + offset;
13216     if (lastLoadPositionFP == NULL) {
13217         DisplayError(_("No position has been loaded yet"), 0);
13218         return FALSE;
13219     }
13220     if (positionNumber <= 0) {
13221         DisplayError(_("Can't back up any further"), 0);
13222         return FALSE;
13223     }
13224     return LoadPosition(lastLoadPositionFP, positionNumber,
13225                         lastLoadPositionTitle);
13226 }
13227
13228 /* Load the nth position from the given file */
13229 int
13230 LoadPositionFromFile (char *filename, int n, char *title)
13231 {
13232     FILE *f;
13233     char buf[MSG_SIZ];
13234
13235     if (strcmp(filename, "-") == 0) {
13236         return LoadPosition(stdin, n, "stdin");
13237     } else {
13238         f = fopen(filename, "rb");
13239         if (f == NULL) {
13240             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13241             DisplayError(buf, errno);
13242             return FALSE;
13243         } else {
13244             return LoadPosition(f, n, title);
13245         }
13246     }
13247 }
13248
13249 /* Load the nth position from the given open file, and close it */
13250 int
13251 LoadPosition (FILE *f, int positionNumber, char *title)
13252 {
13253     char *p, line[MSG_SIZ];
13254     Board initial_position;
13255     int i, j, fenMode, pn;
13256
13257     if (gameMode == Training )
13258         SetTrainingModeOff();
13259
13260     if (gameMode != BeginningOfGame) {
13261         Reset(FALSE, TRUE);
13262     }
13263     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13264         fclose(lastLoadPositionFP);
13265     }
13266     if (positionNumber == 0) positionNumber = 1;
13267     lastLoadPositionFP = f;
13268     lastLoadPositionNumber = positionNumber;
13269     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13270     if (first.pr == NoProc && !appData.noChessProgram) {
13271       StartChessProgram(&first);
13272       InitChessProgram(&first, FALSE);
13273     }
13274     pn = positionNumber;
13275     if (positionNumber < 0) {
13276         /* Negative position number means to seek to that byte offset */
13277         if (fseek(f, -positionNumber, 0) == -1) {
13278             DisplayError(_("Can't seek on position file"), 0);
13279             return FALSE;
13280         };
13281         pn = 1;
13282     } else {
13283         if (fseek(f, 0, 0) == -1) {
13284             if (f == lastLoadPositionFP ?
13285                 positionNumber == lastLoadPositionNumber + 1 :
13286                 positionNumber == 1) {
13287                 pn = 1;
13288             } else {
13289                 DisplayError(_("Can't seek on position file"), 0);
13290                 return FALSE;
13291             }
13292         }
13293     }
13294     /* See if this file is FEN or old-style xboard */
13295     if (fgets(line, MSG_SIZ, f) == NULL) {
13296         DisplayError(_("Position not found in file"), 0);
13297         return FALSE;
13298     }
13299     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13300     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13301
13302     if (pn >= 2) {
13303         if (fenMode || line[0] == '#') pn--;
13304         while (pn > 0) {
13305             /* skip positions before number pn */
13306             if (fgets(line, MSG_SIZ, f) == NULL) {
13307                 Reset(TRUE, TRUE);
13308                 DisplayError(_("Position not found in file"), 0);
13309                 return FALSE;
13310             }
13311             if (fenMode || line[0] == '#') pn--;
13312         }
13313     }
13314
13315     if (fenMode) {
13316         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13317             DisplayError(_("Bad FEN position in file"), 0);
13318             return FALSE;
13319         }
13320     } else {
13321         (void) fgets(line, MSG_SIZ, f);
13322         (void) fgets(line, MSG_SIZ, f);
13323
13324         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13325             (void) fgets(line, MSG_SIZ, f);
13326             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13327                 if (*p == ' ')
13328                   continue;
13329                 initial_position[i][j++] = CharToPiece(*p);
13330             }
13331         }
13332
13333         blackPlaysFirst = FALSE;
13334         if (!feof(f)) {
13335             (void) fgets(line, MSG_SIZ, f);
13336             if (strncmp(line, "black", strlen("black"))==0)
13337               blackPlaysFirst = TRUE;
13338         }
13339     }
13340     startedFromSetupPosition = TRUE;
13341
13342     CopyBoard(boards[0], initial_position);
13343     if (blackPlaysFirst) {
13344         currentMove = forwardMostMove = backwardMostMove = 1;
13345         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13346         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13347         CopyBoard(boards[1], initial_position);
13348         DisplayMessage("", _("Black to play"));
13349     } else {
13350         currentMove = forwardMostMove = backwardMostMove = 0;
13351         DisplayMessage("", _("White to play"));
13352     }
13353     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13354     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13355         SendToProgram("force\n", &first);
13356         SendBoard(&first, forwardMostMove);
13357     }
13358     if (appData.debugMode) {
13359 int i, j;
13360   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13361   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13362         fprintf(debugFP, "Load Position\n");
13363     }
13364
13365     if (positionNumber > 1) {
13366       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13367         DisplayTitle(line);
13368     } else {
13369         DisplayTitle(title);
13370     }
13371     gameMode = EditGame;
13372     ModeHighlight();
13373     ResetClocks();
13374     timeRemaining[0][1] = whiteTimeRemaining;
13375     timeRemaining[1][1] = blackTimeRemaining;
13376     DrawPosition(FALSE, boards[currentMove]);
13377
13378     return TRUE;
13379 }
13380
13381
13382 void
13383 CopyPlayerNameIntoFileName (char **dest, char *src)
13384 {
13385     while (*src != NULLCHAR && *src != ',') {
13386         if (*src == ' ') {
13387             *(*dest)++ = '_';
13388             src++;
13389         } else {
13390             *(*dest)++ = *src++;
13391         }
13392     }
13393 }
13394
13395 char *
13396 DefaultFileName (char *ext)
13397 {
13398     static char def[MSG_SIZ];
13399     char *p;
13400
13401     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13402         p = def;
13403         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13404         *p++ = '-';
13405         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13406         *p++ = '.';
13407         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13408     } else {
13409         def[0] = NULLCHAR;
13410     }
13411     return def;
13412 }
13413
13414 /* Save the current game to the given file */
13415 int
13416 SaveGameToFile (char *filename, int append)
13417 {
13418     FILE *f;
13419     char buf[MSG_SIZ];
13420     int result, i, t,tot=0;
13421
13422     if (strcmp(filename, "-") == 0) {
13423         return SaveGame(stdout, 0, NULL);
13424     } else {
13425         for(i=0; i<10; i++) { // upto 10 tries
13426              f = fopen(filename, append ? "a" : "w");
13427              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13428              if(f || errno != 13) break;
13429              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13430              tot += t;
13431         }
13432         if (f == NULL) {
13433             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13434             DisplayError(buf, errno);
13435             return FALSE;
13436         } else {
13437             safeStrCpy(buf, lastMsg, MSG_SIZ);
13438             DisplayMessage(_("Waiting for access to save file"), "");
13439             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13440             DisplayMessage(_("Saving game"), "");
13441             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13442             result = SaveGame(f, 0, NULL);
13443             DisplayMessage(buf, "");
13444             return result;
13445         }
13446     }
13447 }
13448
13449 char *
13450 SavePart (char *str)
13451 {
13452     static char buf[MSG_SIZ];
13453     char *p;
13454
13455     p = strchr(str, ' ');
13456     if (p == NULL) return str;
13457     strncpy(buf, str, p - str);
13458     buf[p - str] = NULLCHAR;
13459     return buf;
13460 }
13461
13462 #define PGN_MAX_LINE 75
13463
13464 #define PGN_SIDE_WHITE  0
13465 #define PGN_SIDE_BLACK  1
13466
13467 static int
13468 FindFirstMoveOutOfBook (int side)
13469 {
13470     int result = -1;
13471
13472     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13473         int index = backwardMostMove;
13474         int has_book_hit = 0;
13475
13476         if( (index % 2) != side ) {
13477             index++;
13478         }
13479
13480         while( index < forwardMostMove ) {
13481             /* Check to see if engine is in book */
13482             int depth = pvInfoList[index].depth;
13483             int score = pvInfoList[index].score;
13484             int in_book = 0;
13485
13486             if( depth <= 2 ) {
13487                 in_book = 1;
13488             }
13489             else if( score == 0 && depth == 63 ) {
13490                 in_book = 1; /* Zappa */
13491             }
13492             else if( score == 2 && depth == 99 ) {
13493                 in_book = 1; /* Abrok */
13494             }
13495
13496             has_book_hit += in_book;
13497
13498             if( ! in_book ) {
13499                 result = index;
13500
13501                 break;
13502             }
13503
13504             index += 2;
13505         }
13506     }
13507
13508     return result;
13509 }
13510
13511 void
13512 GetOutOfBookInfo (char * buf)
13513 {
13514     int oob[2];
13515     int i;
13516     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13517
13518     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13519     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13520
13521     *buf = '\0';
13522
13523     if( oob[0] >= 0 || oob[1] >= 0 ) {
13524         for( i=0; i<2; i++ ) {
13525             int idx = oob[i];
13526
13527             if( idx >= 0 ) {
13528                 if( i > 0 && oob[0] >= 0 ) {
13529                     strcat( buf, "   " );
13530                 }
13531
13532                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13533                 sprintf( buf+strlen(buf), "%s%.2f",
13534                     pvInfoList[idx].score >= 0 ? "+" : "",
13535                     pvInfoList[idx].score / 100.0 );
13536             }
13537         }
13538     }
13539 }
13540
13541 /* Save game in PGN style */
13542 static void
13543 SaveGamePGN2 (FILE *f)
13544 {
13545     int i, offset, linelen, newblock;
13546 //    char *movetext;
13547     char numtext[32];
13548     int movelen, numlen, blank;
13549     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13550
13551     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13552
13553     PrintPGNTags(f, &gameInfo);
13554
13555     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13556
13557     if (backwardMostMove > 0 || startedFromSetupPosition) {
13558         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13559         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13560         fprintf(f, "\n{--------------\n");
13561         PrintPosition(f, backwardMostMove);
13562         fprintf(f, "--------------}\n");
13563         free(fen);
13564     }
13565     else {
13566         /* [AS] Out of book annotation */
13567         if( appData.saveOutOfBookInfo ) {
13568             char buf[64];
13569
13570             GetOutOfBookInfo( buf );
13571
13572             if( buf[0] != '\0' ) {
13573                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13574             }
13575         }
13576
13577         fprintf(f, "\n");
13578     }
13579
13580     i = backwardMostMove;
13581     linelen = 0;
13582     newblock = TRUE;
13583
13584     while (i < forwardMostMove) {
13585         /* Print comments preceding this move */
13586         if (commentList[i] != NULL) {
13587             if (linelen > 0) fprintf(f, "\n");
13588             fprintf(f, "%s", commentList[i]);
13589             linelen = 0;
13590             newblock = TRUE;
13591         }
13592
13593         /* Format move number */
13594         if ((i % 2) == 0)
13595           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13596         else
13597           if (newblock)
13598             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13599           else
13600             numtext[0] = NULLCHAR;
13601
13602         numlen = strlen(numtext);
13603         newblock = FALSE;
13604
13605         /* Print move number */
13606         blank = linelen > 0 && numlen > 0;
13607         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13608             fprintf(f, "\n");
13609             linelen = 0;
13610             blank = 0;
13611         }
13612         if (blank) {
13613             fprintf(f, " ");
13614             linelen++;
13615         }
13616         fprintf(f, "%s", numtext);
13617         linelen += numlen;
13618
13619         /* Get move */
13620         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13621         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13622
13623         /* Print move */
13624         blank = linelen > 0 && movelen > 0;
13625         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13626             fprintf(f, "\n");
13627             linelen = 0;
13628             blank = 0;
13629         }
13630         if (blank) {
13631             fprintf(f, " ");
13632             linelen++;
13633         }
13634         fprintf(f, "%s", move_buffer);
13635         linelen += movelen;
13636
13637         /* [AS] Add PV info if present */
13638         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13639             /* [HGM] add time */
13640             char buf[MSG_SIZ]; int seconds;
13641
13642             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13643
13644             if( seconds <= 0)
13645               buf[0] = 0;
13646             else
13647               if( seconds < 30 )
13648                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13649               else
13650                 {
13651                   seconds = (seconds + 4)/10; // round to full seconds
13652                   if( seconds < 60 )
13653                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13654                   else
13655                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13656                 }
13657
13658             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13659                       pvInfoList[i].score >= 0 ? "+" : "",
13660                       pvInfoList[i].score / 100.0,
13661                       pvInfoList[i].depth,
13662                       buf );
13663
13664             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13665
13666             /* Print score/depth */
13667             blank = linelen > 0 && movelen > 0;
13668             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13669                 fprintf(f, "\n");
13670                 linelen = 0;
13671                 blank = 0;
13672             }
13673             if (blank) {
13674                 fprintf(f, " ");
13675                 linelen++;
13676             }
13677             fprintf(f, "%s", move_buffer);
13678             linelen += movelen;
13679         }
13680
13681         i++;
13682     }
13683
13684     /* Start a new line */
13685     if (linelen > 0) fprintf(f, "\n");
13686
13687     /* Print comments after last move */
13688     if (commentList[i] != NULL) {
13689         fprintf(f, "%s\n", commentList[i]);
13690     }
13691
13692     /* Print result */
13693     if (gameInfo.resultDetails != NULL &&
13694         gameInfo.resultDetails[0] != NULLCHAR) {
13695         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13696         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13697            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13698             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13699         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13700     } else {
13701         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13702     }
13703 }
13704
13705 /* Save game in PGN style and close the file */
13706 int
13707 SaveGamePGN (FILE *f)
13708 {
13709     SaveGamePGN2(f);
13710     fclose(f);
13711     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13712     return TRUE;
13713 }
13714
13715 /* Save game in old style and close the file */
13716 int
13717 SaveGameOldStyle (FILE *f)
13718 {
13719     int i, offset;
13720     time_t tm;
13721
13722     tm = time((time_t *) NULL);
13723
13724     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13725     PrintOpponents(f);
13726
13727     if (backwardMostMove > 0 || startedFromSetupPosition) {
13728         fprintf(f, "\n[--------------\n");
13729         PrintPosition(f, backwardMostMove);
13730         fprintf(f, "--------------]\n");
13731     } else {
13732         fprintf(f, "\n");
13733     }
13734
13735     i = backwardMostMove;
13736     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13737
13738     while (i < forwardMostMove) {
13739         if (commentList[i] != NULL) {
13740             fprintf(f, "[%s]\n", commentList[i]);
13741         }
13742
13743         if ((i % 2) == 1) {
13744             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13745             i++;
13746         } else {
13747             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13748             i++;
13749             if (commentList[i] != NULL) {
13750                 fprintf(f, "\n");
13751                 continue;
13752             }
13753             if (i >= forwardMostMove) {
13754                 fprintf(f, "\n");
13755                 break;
13756             }
13757             fprintf(f, "%s\n", parseList[i]);
13758             i++;
13759         }
13760     }
13761
13762     if (commentList[i] != NULL) {
13763         fprintf(f, "[%s]\n", commentList[i]);
13764     }
13765
13766     /* This isn't really the old style, but it's close enough */
13767     if (gameInfo.resultDetails != NULL &&
13768         gameInfo.resultDetails[0] != NULLCHAR) {
13769         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13770                 gameInfo.resultDetails);
13771     } else {
13772         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13773     }
13774
13775     fclose(f);
13776     return TRUE;
13777 }
13778
13779 /* Save the current game to open file f and close the file */
13780 int
13781 SaveGame (FILE *f, int dummy, char *dummy2)
13782 {
13783     if (gameMode == EditPosition) EditPositionDone(TRUE);
13784     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13785     if (appData.oldSaveStyle)
13786       return SaveGameOldStyle(f);
13787     else
13788       return SaveGamePGN(f);
13789 }
13790
13791 /* Save the current position to the given file */
13792 int
13793 SavePositionToFile (char *filename)
13794 {
13795     FILE *f;
13796     char buf[MSG_SIZ];
13797
13798     if (strcmp(filename, "-") == 0) {
13799         return SavePosition(stdout, 0, NULL);
13800     } else {
13801         f = fopen(filename, "a");
13802         if (f == NULL) {
13803             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13804             DisplayError(buf, errno);
13805             return FALSE;
13806         } else {
13807             safeStrCpy(buf, lastMsg, MSG_SIZ);
13808             DisplayMessage(_("Waiting for access to save file"), "");
13809             flock(fileno(f), LOCK_EX); // [HGM] lock
13810             DisplayMessage(_("Saving position"), "");
13811             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13812             SavePosition(f, 0, NULL);
13813             DisplayMessage(buf, "");
13814             return TRUE;
13815         }
13816     }
13817 }
13818
13819 /* Save the current position to the given open file and close the file */
13820 int
13821 SavePosition (FILE *f, int dummy, char *dummy2)
13822 {
13823     time_t tm;
13824     char *fen;
13825
13826     if (gameMode == EditPosition) EditPositionDone(TRUE);
13827     if (appData.oldSaveStyle) {
13828         tm = time((time_t *) NULL);
13829
13830         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13831         PrintOpponents(f);
13832         fprintf(f, "[--------------\n");
13833         PrintPosition(f, currentMove);
13834         fprintf(f, "--------------]\n");
13835     } else {
13836         fen = PositionToFEN(currentMove, NULL, 1);
13837         fprintf(f, "%s\n", fen);
13838         free(fen);
13839     }
13840     fclose(f);
13841     return TRUE;
13842 }
13843
13844 void
13845 ReloadCmailMsgEvent (int unregister)
13846 {
13847 #if !WIN32
13848     static char *inFilename = NULL;
13849     static char *outFilename;
13850     int i;
13851     struct stat inbuf, outbuf;
13852     int status;
13853
13854     /* Any registered moves are unregistered if unregister is set, */
13855     /* i.e. invoked by the signal handler */
13856     if (unregister) {
13857         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13858             cmailMoveRegistered[i] = FALSE;
13859             if (cmailCommentList[i] != NULL) {
13860                 free(cmailCommentList[i]);
13861                 cmailCommentList[i] = NULL;
13862             }
13863         }
13864         nCmailMovesRegistered = 0;
13865     }
13866
13867     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13868         cmailResult[i] = CMAIL_NOT_RESULT;
13869     }
13870     nCmailResults = 0;
13871
13872     if (inFilename == NULL) {
13873         /* Because the filenames are static they only get malloced once  */
13874         /* and they never get freed                                      */
13875         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13876         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13877
13878         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13879         sprintf(outFilename, "%s.out", appData.cmailGameName);
13880     }
13881
13882     status = stat(outFilename, &outbuf);
13883     if (status < 0) {
13884         cmailMailedMove = FALSE;
13885     } else {
13886         status = stat(inFilename, &inbuf);
13887         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13888     }
13889
13890     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13891        counts the games, notes how each one terminated, etc.
13892
13893        It would be nice to remove this kludge and instead gather all
13894        the information while building the game list.  (And to keep it
13895        in the game list nodes instead of having a bunch of fixed-size
13896        parallel arrays.)  Note this will require getting each game's
13897        termination from the PGN tags, as the game list builder does
13898        not process the game moves.  --mann
13899        */
13900     cmailMsgLoaded = TRUE;
13901     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13902
13903     /* Load first game in the file or popup game menu */
13904     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13905
13906 #endif /* !WIN32 */
13907     return;
13908 }
13909
13910 int
13911 RegisterMove ()
13912 {
13913     FILE *f;
13914     char string[MSG_SIZ];
13915
13916     if (   cmailMailedMove
13917         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13918         return TRUE;            /* Allow free viewing  */
13919     }
13920
13921     /* Unregister move to ensure that we don't leave RegisterMove        */
13922     /* with the move registered when the conditions for registering no   */
13923     /* longer hold                                                       */
13924     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13925         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13926         nCmailMovesRegistered --;
13927
13928         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13929           {
13930               free(cmailCommentList[lastLoadGameNumber - 1]);
13931               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13932           }
13933     }
13934
13935     if (cmailOldMove == -1) {
13936         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13937         return FALSE;
13938     }
13939
13940     if (currentMove > cmailOldMove + 1) {
13941         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13942         return FALSE;
13943     }
13944
13945     if (currentMove < cmailOldMove) {
13946         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13947         return FALSE;
13948     }
13949
13950     if (forwardMostMove > currentMove) {
13951         /* Silently truncate extra moves */
13952         TruncateGame();
13953     }
13954
13955     if (   (currentMove == cmailOldMove + 1)
13956         || (   (currentMove == cmailOldMove)
13957             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13958                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13959         if (gameInfo.result != GameUnfinished) {
13960             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13961         }
13962
13963         if (commentList[currentMove] != NULL) {
13964             cmailCommentList[lastLoadGameNumber - 1]
13965               = StrSave(commentList[currentMove]);
13966         }
13967         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13968
13969         if (appData.debugMode)
13970           fprintf(debugFP, "Saving %s for game %d\n",
13971                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13972
13973         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13974
13975         f = fopen(string, "w");
13976         if (appData.oldSaveStyle) {
13977             SaveGameOldStyle(f); /* also closes the file */
13978
13979             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13980             f = fopen(string, "w");
13981             SavePosition(f, 0, NULL); /* also closes the file */
13982         } else {
13983             fprintf(f, "{--------------\n");
13984             PrintPosition(f, currentMove);
13985             fprintf(f, "--------------}\n\n");
13986
13987             SaveGame(f, 0, NULL); /* also closes the file*/
13988         }
13989
13990         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13991         nCmailMovesRegistered ++;
13992     } else if (nCmailGames == 1) {
13993         DisplayError(_("You have not made a move yet"), 0);
13994         return FALSE;
13995     }
13996
13997     return TRUE;
13998 }
13999
14000 void
14001 MailMoveEvent ()
14002 {
14003 #if !WIN32
14004     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14005     FILE *commandOutput;
14006     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14007     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14008     int nBuffers;
14009     int i;
14010     int archived;
14011     char *arcDir;
14012
14013     if (! cmailMsgLoaded) {
14014         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14015         return;
14016     }
14017
14018     if (nCmailGames == nCmailResults) {
14019         DisplayError(_("No unfinished games"), 0);
14020         return;
14021     }
14022
14023 #if CMAIL_PROHIBIT_REMAIL
14024     if (cmailMailedMove) {
14025       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);
14026         DisplayError(msg, 0);
14027         return;
14028     }
14029 #endif
14030
14031     if (! (cmailMailedMove || RegisterMove())) return;
14032
14033     if (   cmailMailedMove
14034         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14035       snprintf(string, MSG_SIZ, partCommandString,
14036                appData.debugMode ? " -v" : "", appData.cmailGameName);
14037         commandOutput = popen(string, "r");
14038
14039         if (commandOutput == NULL) {
14040             DisplayError(_("Failed to invoke cmail"), 0);
14041         } else {
14042             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14043                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14044             }
14045             if (nBuffers > 1) {
14046                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14047                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14048                 nBytes = MSG_SIZ - 1;
14049             } else {
14050                 (void) memcpy(msg, buffer, nBytes);
14051             }
14052             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14053
14054             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14055                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14056
14057                 archived = TRUE;
14058                 for (i = 0; i < nCmailGames; i ++) {
14059                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14060                         archived = FALSE;
14061                     }
14062                 }
14063                 if (   archived
14064                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14065                         != NULL)) {
14066                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14067                            arcDir,
14068                            appData.cmailGameName,
14069                            gameInfo.date);
14070                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14071                     cmailMsgLoaded = FALSE;
14072                 }
14073             }
14074
14075             DisplayInformation(msg);
14076             pclose(commandOutput);
14077         }
14078     } else {
14079         if ((*cmailMsg) != '\0') {
14080             DisplayInformation(cmailMsg);
14081         }
14082     }
14083
14084     return;
14085 #endif /* !WIN32 */
14086 }
14087
14088 char *
14089 CmailMsg ()
14090 {
14091 #if WIN32
14092     return NULL;
14093 #else
14094     int  prependComma = 0;
14095     char number[5];
14096     char string[MSG_SIZ];       /* Space for game-list */
14097     int  i;
14098
14099     if (!cmailMsgLoaded) return "";
14100
14101     if (cmailMailedMove) {
14102       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14103     } else {
14104         /* Create a list of games left */
14105       snprintf(string, MSG_SIZ, "[");
14106         for (i = 0; i < nCmailGames; i ++) {
14107             if (! (   cmailMoveRegistered[i]
14108                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14109                 if (prependComma) {
14110                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14111                 } else {
14112                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14113                     prependComma = 1;
14114                 }
14115
14116                 strcat(string, number);
14117             }
14118         }
14119         strcat(string, "]");
14120
14121         if (nCmailMovesRegistered + nCmailResults == 0) {
14122             switch (nCmailGames) {
14123               case 1:
14124                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14125                 break;
14126
14127               case 2:
14128                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14129                 break;
14130
14131               default:
14132                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14133                          nCmailGames);
14134                 break;
14135             }
14136         } else {
14137             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14138               case 1:
14139                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14140                          string);
14141                 break;
14142
14143               case 0:
14144                 if (nCmailResults == nCmailGames) {
14145                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14146                 } else {
14147                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14148                 }
14149                 break;
14150
14151               default:
14152                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14153                          string);
14154             }
14155         }
14156     }
14157     return cmailMsg;
14158 #endif /* WIN32 */
14159 }
14160
14161 void
14162 ResetGameEvent ()
14163 {
14164     if (gameMode == Training)
14165       SetTrainingModeOff();
14166
14167     Reset(TRUE, TRUE);
14168     cmailMsgLoaded = FALSE;
14169     if (appData.icsActive) {
14170       SendToICS(ics_prefix);
14171       SendToICS("refresh\n");
14172     }
14173 }
14174
14175 void
14176 ExitEvent (int status)
14177 {
14178     exiting++;
14179     if (exiting > 2) {
14180       /* Give up on clean exit */
14181       exit(status);
14182     }
14183     if (exiting > 1) {
14184       /* Keep trying for clean exit */
14185       return;
14186     }
14187
14188     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14189     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14190
14191     if (telnetISR != NULL) {
14192       RemoveInputSource(telnetISR);
14193     }
14194     if (icsPR != NoProc) {
14195       DestroyChildProcess(icsPR, TRUE);
14196     }
14197
14198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14200
14201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14202     /* make sure this other one finishes before killing it!                  */
14203     if(endingGame) { int count = 0;
14204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14205         while(endingGame && count++ < 10) DoSleep(1);
14206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14207     }
14208
14209     /* Kill off chess programs */
14210     if (first.pr != NoProc) {
14211         ExitAnalyzeMode();
14212
14213         DoSleep( appData.delayBeforeQuit );
14214         SendToProgram("quit\n", &first);
14215         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14216     }
14217     if (second.pr != NoProc) {
14218         DoSleep( appData.delayBeforeQuit );
14219         SendToProgram("quit\n", &second);
14220         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14221     }
14222     if (first.isr != NULL) {
14223         RemoveInputSource(first.isr);
14224     }
14225     if (second.isr != NULL) {
14226         RemoveInputSource(second.isr);
14227     }
14228
14229     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14230     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14231
14232     ShutDownFrontEnd();
14233     exit(status);
14234 }
14235
14236 void
14237 PauseEngine (ChessProgramState *cps)
14238 {
14239     SendToProgram("pause\n", cps);
14240     cps->pause = 2;
14241 }
14242
14243 void
14244 UnPauseEngine (ChessProgramState *cps)
14245 {
14246     SendToProgram("resume\n", cps);
14247     cps->pause = 1;
14248 }
14249
14250 void
14251 PauseEvent ()
14252 {
14253     if (appData.debugMode)
14254         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14255     if (pausing) {
14256         pausing = FALSE;
14257         ModeHighlight();
14258         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14259             StartClocks();
14260             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14261                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14262                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14263             }
14264             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14265             HandleMachineMove(stashedInputMove, stalledEngine);
14266             stalledEngine = NULL;
14267             return;
14268         }
14269         if (gameMode == MachinePlaysWhite ||
14270             gameMode == TwoMachinesPlay   ||
14271             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14272             if(first.pause)  UnPauseEngine(&first);
14273             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14274             if(second.pause) UnPauseEngine(&second);
14275             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14276             StartClocks();
14277         } else {
14278             DisplayBothClocks();
14279         }
14280         if (gameMode == PlayFromGameFile) {
14281             if (appData.timeDelay >= 0)
14282                 AutoPlayGameLoop();
14283         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14284             Reset(FALSE, TRUE);
14285             SendToICS(ics_prefix);
14286             SendToICS("refresh\n");
14287         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14288             ForwardInner(forwardMostMove);
14289         }
14290         pauseExamInvalid = FALSE;
14291     } else {
14292         switch (gameMode) {
14293           default:
14294             return;
14295           case IcsExamining:
14296             pauseExamForwardMostMove = forwardMostMove;
14297             pauseExamInvalid = FALSE;
14298             /* fall through */
14299           case IcsObserving:
14300           case IcsPlayingWhite:
14301           case IcsPlayingBlack:
14302             pausing = TRUE;
14303             ModeHighlight();
14304             return;
14305           case PlayFromGameFile:
14306             (void) StopLoadGameTimer();
14307             pausing = TRUE;
14308             ModeHighlight();
14309             break;
14310           case BeginningOfGame:
14311             if (appData.icsActive) return;
14312             /* else fall through */
14313           case MachinePlaysWhite:
14314           case MachinePlaysBlack:
14315           case TwoMachinesPlay:
14316             if (forwardMostMove == 0)
14317               return;           /* don't pause if no one has moved */
14318             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14319                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14320                 if(onMove->pause) {           // thinking engine can be paused
14321                     PauseEngine(onMove);      // do it
14322                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14323                         PauseEngine(onMove->other);
14324                     else
14325                         SendToProgram("easy\n", onMove->other);
14326                     StopClocks();
14327                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14328             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14329                 if(first.pause) {
14330                     PauseEngine(&first);
14331                     StopClocks();
14332                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14333             } else { // human on move, pause pondering by either method
14334                 if(first.pause)
14335                     PauseEngine(&first);
14336                 else if(appData.ponderNextMove)
14337                     SendToProgram("easy\n", &first);
14338                 StopClocks();
14339             }
14340             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14341           case AnalyzeMode:
14342             pausing = TRUE;
14343             ModeHighlight();
14344             break;
14345         }
14346     }
14347 }
14348
14349 void
14350 EditCommentEvent ()
14351 {
14352     char title[MSG_SIZ];
14353
14354     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14355       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14356     } else {
14357       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14358                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14359                parseList[currentMove - 1]);
14360     }
14361
14362     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14363 }
14364
14365
14366 void
14367 EditTagsEvent ()
14368 {
14369     char *tags = PGNTags(&gameInfo);
14370     bookUp = FALSE;
14371     EditTagsPopUp(tags, NULL);
14372     free(tags);
14373 }
14374
14375 void
14376 ToggleSecond ()
14377 {
14378   if(second.analyzing) {
14379     SendToProgram("exit\n", &second);
14380     second.analyzing = FALSE;
14381   } else {
14382     if (second.pr == NoProc) StartChessProgram(&second);
14383     InitChessProgram(&second, FALSE);
14384     FeedMovesToProgram(&second, currentMove);
14385
14386     SendToProgram("analyze\n", &second);
14387     second.analyzing = TRUE;
14388   }
14389 }
14390
14391 /* Toggle ShowThinking */
14392 void
14393 ToggleShowThinking()
14394 {
14395   appData.showThinking = !appData.showThinking;
14396   ShowThinkingEvent();
14397 }
14398
14399 int
14400 AnalyzeModeEvent ()
14401 {
14402     char buf[MSG_SIZ];
14403
14404     if (!first.analysisSupport) {
14405       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14406       DisplayError(buf, 0);
14407       return 0;
14408     }
14409     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14410     if (appData.icsActive) {
14411         if (gameMode != IcsObserving) {
14412           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14413             DisplayError(buf, 0);
14414             /* secure check */
14415             if (appData.icsEngineAnalyze) {
14416                 if (appData.debugMode)
14417                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14418                 ExitAnalyzeMode();
14419                 ModeHighlight();
14420             }
14421             return 0;
14422         }
14423         /* if enable, user wants to disable icsEngineAnalyze */
14424         if (appData.icsEngineAnalyze) {
14425                 ExitAnalyzeMode();
14426                 ModeHighlight();
14427                 return 0;
14428         }
14429         appData.icsEngineAnalyze = TRUE;
14430         if (appData.debugMode)
14431             fprintf(debugFP, "ICS engine analyze starting... \n");
14432     }
14433
14434     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14435     if (appData.noChessProgram || gameMode == AnalyzeMode)
14436       return 0;
14437
14438     if (gameMode != AnalyzeFile) {
14439         if (!appData.icsEngineAnalyze) {
14440                EditGameEvent();
14441                if (gameMode != EditGame) return 0;
14442         }
14443         if (!appData.showThinking) ToggleShowThinking();
14444         ResurrectChessProgram();
14445         SendToProgram("analyze\n", &first);
14446         first.analyzing = TRUE;
14447         /*first.maybeThinking = TRUE;*/
14448         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14449         EngineOutputPopUp();
14450     }
14451     if (!appData.icsEngineAnalyze) {
14452         gameMode = AnalyzeMode;
14453         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14454     }
14455     pausing = FALSE;
14456     ModeHighlight();
14457     SetGameInfo();
14458
14459     StartAnalysisClock();
14460     GetTimeMark(&lastNodeCountTime);
14461     lastNodeCount = 0;
14462     return 1;
14463 }
14464
14465 void
14466 AnalyzeFileEvent ()
14467 {
14468     if (appData.noChessProgram || gameMode == AnalyzeFile)
14469       return;
14470
14471     if (!first.analysisSupport) {
14472       char buf[MSG_SIZ];
14473       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14474       DisplayError(buf, 0);
14475       return;
14476     }
14477
14478     if (gameMode != AnalyzeMode) {
14479         keepInfo = 1; // mere annotating should not alter PGN tags
14480         EditGameEvent();
14481         keepInfo = 0;
14482         if (gameMode != EditGame) return;
14483         if (!appData.showThinking) ToggleShowThinking();
14484         ResurrectChessProgram();
14485         SendToProgram("analyze\n", &first);
14486         first.analyzing = TRUE;
14487         /*first.maybeThinking = TRUE;*/
14488         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14489         EngineOutputPopUp();
14490     }
14491     gameMode = AnalyzeFile;
14492     pausing = FALSE;
14493     ModeHighlight();
14494
14495     StartAnalysisClock();
14496     GetTimeMark(&lastNodeCountTime);
14497     lastNodeCount = 0;
14498     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14499     AnalysisPeriodicEvent(1);
14500 }
14501
14502 void
14503 MachineWhiteEvent ()
14504 {
14505     char buf[MSG_SIZ];
14506     char *bookHit = NULL;
14507
14508     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14509       return;
14510
14511
14512     if (gameMode == PlayFromGameFile ||
14513         gameMode == TwoMachinesPlay  ||
14514         gameMode == Training         ||
14515         gameMode == AnalyzeMode      ||
14516         gameMode == EndOfGame)
14517         EditGameEvent();
14518
14519     if (gameMode == EditPosition)
14520         EditPositionDone(TRUE);
14521
14522     if (!WhiteOnMove(currentMove)) {
14523         DisplayError(_("It is not White's turn"), 0);
14524         return;
14525     }
14526
14527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14528       ExitAnalyzeMode();
14529
14530     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14531         gameMode == AnalyzeFile)
14532         TruncateGame();
14533
14534     ResurrectChessProgram();    /* in case it isn't running */
14535     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14536         gameMode = MachinePlaysWhite;
14537         ResetClocks();
14538     } else
14539     gameMode = MachinePlaysWhite;
14540     pausing = FALSE;
14541     ModeHighlight();
14542     SetGameInfo();
14543     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14544     DisplayTitle(buf);
14545     if (first.sendName) {
14546       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14547       SendToProgram(buf, &first);
14548     }
14549     if (first.sendTime) {
14550       if (first.useColors) {
14551         SendToProgram("black\n", &first); /*gnu kludge*/
14552       }
14553       SendTimeRemaining(&first, TRUE);
14554     }
14555     if (first.useColors) {
14556       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14557     }
14558     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14559     SetMachineThinkingEnables();
14560     first.maybeThinking = TRUE;
14561     StartClocks();
14562     firstMove = FALSE;
14563
14564     if (appData.autoFlipView && !flipView) {
14565       flipView = !flipView;
14566       DrawPosition(FALSE, NULL);
14567       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14568     }
14569
14570     if(bookHit) { // [HGM] book: simulate book reply
14571         static char bookMove[MSG_SIZ]; // a bit generous?
14572
14573         programStats.nodes = programStats.depth = programStats.time =
14574         programStats.score = programStats.got_only_move = 0;
14575         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14576
14577         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14578         strcat(bookMove, bookHit);
14579         HandleMachineMove(bookMove, &first);
14580     }
14581 }
14582
14583 void
14584 MachineBlackEvent ()
14585 {
14586   char buf[MSG_SIZ];
14587   char *bookHit = NULL;
14588
14589     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14590         return;
14591
14592
14593     if (gameMode == PlayFromGameFile ||
14594         gameMode == TwoMachinesPlay  ||
14595         gameMode == Training         ||
14596         gameMode == AnalyzeMode      ||
14597         gameMode == EndOfGame)
14598         EditGameEvent();
14599
14600     if (gameMode == EditPosition)
14601         EditPositionDone(TRUE);
14602
14603     if (WhiteOnMove(currentMove)) {
14604         DisplayError(_("It is not Black's turn"), 0);
14605         return;
14606     }
14607
14608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14609       ExitAnalyzeMode();
14610
14611     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14612         gameMode == AnalyzeFile)
14613         TruncateGame();
14614
14615     ResurrectChessProgram();    /* in case it isn't running */
14616     gameMode = MachinePlaysBlack;
14617     pausing = FALSE;
14618     ModeHighlight();
14619     SetGameInfo();
14620     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14621     DisplayTitle(buf);
14622     if (first.sendName) {
14623       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14624       SendToProgram(buf, &first);
14625     }
14626     if (first.sendTime) {
14627       if (first.useColors) {
14628         SendToProgram("white\n", &first); /*gnu kludge*/
14629       }
14630       SendTimeRemaining(&first, FALSE);
14631     }
14632     if (first.useColors) {
14633       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14634     }
14635     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14636     SetMachineThinkingEnables();
14637     first.maybeThinking = TRUE;
14638     StartClocks();
14639
14640     if (appData.autoFlipView && flipView) {
14641       flipView = !flipView;
14642       DrawPosition(FALSE, NULL);
14643       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14644     }
14645     if(bookHit) { // [HGM] book: simulate book reply
14646         static char bookMove[MSG_SIZ]; // a bit generous?
14647
14648         programStats.nodes = programStats.depth = programStats.time =
14649         programStats.score = programStats.got_only_move = 0;
14650         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14651
14652         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14653         strcat(bookMove, bookHit);
14654         HandleMachineMove(bookMove, &first);
14655     }
14656 }
14657
14658
14659 void
14660 DisplayTwoMachinesTitle ()
14661 {
14662     char buf[MSG_SIZ];
14663     if (appData.matchGames > 0) {
14664         if(appData.tourneyFile[0]) {
14665           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14666                    gameInfo.white, _("vs."), gameInfo.black,
14667                    nextGame+1, appData.matchGames+1,
14668                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14669         } else
14670         if (first.twoMachinesColor[0] == 'w') {
14671           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14672                    gameInfo.white, _("vs."),  gameInfo.black,
14673                    first.matchWins, second.matchWins,
14674                    matchGame - 1 - (first.matchWins + second.matchWins));
14675         } else {
14676           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14677                    gameInfo.white, _("vs."), gameInfo.black,
14678                    second.matchWins, first.matchWins,
14679                    matchGame - 1 - (first.matchWins + second.matchWins));
14680         }
14681     } else {
14682       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14683     }
14684     DisplayTitle(buf);
14685 }
14686
14687 void
14688 SettingsMenuIfReady ()
14689 {
14690   if (second.lastPing != second.lastPong) {
14691     DisplayMessage("", _("Waiting for second chess program"));
14692     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14693     return;
14694   }
14695   ThawUI();
14696   DisplayMessage("", "");
14697   SettingsPopUp(&second);
14698 }
14699
14700 int
14701 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14702 {
14703     char buf[MSG_SIZ];
14704     if (cps->pr == NoProc) {
14705         StartChessProgram(cps);
14706         if (cps->protocolVersion == 1) {
14707           retry();
14708           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14709         } else {
14710           /* kludge: allow timeout for initial "feature" command */
14711           if(retry != TwoMachinesEventIfReady) FreezeUI();
14712           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14713           DisplayMessage("", buf);
14714           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14715         }
14716         return 1;
14717     }
14718     return 0;
14719 }
14720
14721 void
14722 TwoMachinesEvent P((void))
14723 {
14724     int i;
14725     char buf[MSG_SIZ];
14726     ChessProgramState *onmove;
14727     char *bookHit = NULL;
14728     static int stalling = 0;
14729     TimeMark now;
14730     long wait;
14731
14732     if (appData.noChessProgram) return;
14733
14734     switch (gameMode) {
14735       case TwoMachinesPlay:
14736         return;
14737       case MachinePlaysWhite:
14738       case MachinePlaysBlack:
14739         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14740             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14741             return;
14742         }
14743         /* fall through */
14744       case BeginningOfGame:
14745       case PlayFromGameFile:
14746       case EndOfGame:
14747         EditGameEvent();
14748         if (gameMode != EditGame) return;
14749         break;
14750       case EditPosition:
14751         EditPositionDone(TRUE);
14752         break;
14753       case AnalyzeMode:
14754       case AnalyzeFile:
14755         ExitAnalyzeMode();
14756         break;
14757       case EditGame:
14758       default:
14759         break;
14760     }
14761
14762 //    forwardMostMove = currentMove;
14763     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14764     startingEngine = TRUE;
14765
14766     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14767
14768     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14769     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14770       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14771       return;
14772     }
14773     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14774
14775     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14776                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14777         startingEngine = matchMode = FALSE;
14778         DisplayError("second engine does not play this", 0);
14779         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14780         EditGameEvent(); // switch back to EditGame mode
14781         return;
14782     }
14783
14784     if(!stalling) {
14785       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14786       SendToProgram("force\n", &second);
14787       stalling = 1;
14788       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14789       return;
14790     }
14791     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14792     if(appData.matchPause>10000 || appData.matchPause<10)
14793                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14794     wait = SubtractTimeMarks(&now, &pauseStart);
14795     if(wait < appData.matchPause) {
14796         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14797         return;
14798     }
14799     // we are now committed to starting the game
14800     stalling = 0;
14801     DisplayMessage("", "");
14802     if (startedFromSetupPosition) {
14803         SendBoard(&second, backwardMostMove);
14804     if (appData.debugMode) {
14805         fprintf(debugFP, "Two Machines\n");
14806     }
14807     }
14808     for (i = backwardMostMove; i < forwardMostMove; i++) {
14809         SendMoveToProgram(i, &second);
14810     }
14811
14812     gameMode = TwoMachinesPlay;
14813     pausing = startingEngine = FALSE;
14814     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14815     SetGameInfo();
14816     DisplayTwoMachinesTitle();
14817     firstMove = TRUE;
14818     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14819         onmove = &first;
14820     } else {
14821         onmove = &second;
14822     }
14823     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14824     SendToProgram(first.computerString, &first);
14825     if (first.sendName) {
14826       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14827       SendToProgram(buf, &first);
14828     }
14829     SendToProgram(second.computerString, &second);
14830     if (second.sendName) {
14831       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14832       SendToProgram(buf, &second);
14833     }
14834
14835     ResetClocks();
14836     if (!first.sendTime || !second.sendTime) {
14837         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14838         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14839     }
14840     if (onmove->sendTime) {
14841       if (onmove->useColors) {
14842         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14843       }
14844       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14845     }
14846     if (onmove->useColors) {
14847       SendToProgram(onmove->twoMachinesColor, onmove);
14848     }
14849     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14850 //    SendToProgram("go\n", onmove);
14851     onmove->maybeThinking = TRUE;
14852     SetMachineThinkingEnables();
14853
14854     StartClocks();
14855
14856     if(bookHit) { // [HGM] book: simulate book reply
14857         static char bookMove[MSG_SIZ]; // a bit generous?
14858
14859         programStats.nodes = programStats.depth = programStats.time =
14860         programStats.score = programStats.got_only_move = 0;
14861         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14862
14863         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14864         strcat(bookMove, bookHit);
14865         savedMessage = bookMove; // args for deferred call
14866         savedState = onmove;
14867         ScheduleDelayedEvent(DeferredBookMove, 1);
14868     }
14869 }
14870
14871 void
14872 TrainingEvent ()
14873 {
14874     if (gameMode == Training) {
14875       SetTrainingModeOff();
14876       gameMode = PlayFromGameFile;
14877       DisplayMessage("", _("Training mode off"));
14878     } else {
14879       gameMode = Training;
14880       animateTraining = appData.animate;
14881
14882       /* make sure we are not already at the end of the game */
14883       if (currentMove < forwardMostMove) {
14884         SetTrainingModeOn();
14885         DisplayMessage("", _("Training mode on"));
14886       } else {
14887         gameMode = PlayFromGameFile;
14888         DisplayError(_("Already at end of game"), 0);
14889       }
14890     }
14891     ModeHighlight();
14892 }
14893
14894 void
14895 IcsClientEvent ()
14896 {
14897     if (!appData.icsActive) return;
14898     switch (gameMode) {
14899       case IcsPlayingWhite:
14900       case IcsPlayingBlack:
14901       case IcsObserving:
14902       case IcsIdle:
14903       case BeginningOfGame:
14904       case IcsExamining:
14905         return;
14906
14907       case EditGame:
14908         break;
14909
14910       case EditPosition:
14911         EditPositionDone(TRUE);
14912         break;
14913
14914       case AnalyzeMode:
14915       case AnalyzeFile:
14916         ExitAnalyzeMode();
14917         break;
14918
14919       default:
14920         EditGameEvent();
14921         break;
14922     }
14923
14924     gameMode = IcsIdle;
14925     ModeHighlight();
14926     return;
14927 }
14928
14929 void
14930 EditGameEvent ()
14931 {
14932     int i;
14933
14934     switch (gameMode) {
14935       case Training:
14936         SetTrainingModeOff();
14937         break;
14938       case MachinePlaysWhite:
14939       case MachinePlaysBlack:
14940       case BeginningOfGame:
14941         SendToProgram("force\n", &first);
14942         SetUserThinkingEnables();
14943         break;
14944       case PlayFromGameFile:
14945         (void) StopLoadGameTimer();
14946         if (gameFileFP != NULL) {
14947             gameFileFP = NULL;
14948         }
14949         break;
14950       case EditPosition:
14951         EditPositionDone(TRUE);
14952         break;
14953       case AnalyzeMode:
14954       case AnalyzeFile:
14955         ExitAnalyzeMode();
14956         SendToProgram("force\n", &first);
14957         break;
14958       case TwoMachinesPlay:
14959         GameEnds(EndOfFile, NULL, GE_PLAYER);
14960         ResurrectChessProgram();
14961         SetUserThinkingEnables();
14962         break;
14963       case EndOfGame:
14964         ResurrectChessProgram();
14965         break;
14966       case IcsPlayingBlack:
14967       case IcsPlayingWhite:
14968         DisplayError(_("Warning: You are still playing a game"), 0);
14969         break;
14970       case IcsObserving:
14971         DisplayError(_("Warning: You are still observing a game"), 0);
14972         break;
14973       case IcsExamining:
14974         DisplayError(_("Warning: You are still examining a game"), 0);
14975         break;
14976       case IcsIdle:
14977         break;
14978       case EditGame:
14979       default:
14980         return;
14981     }
14982
14983     pausing = FALSE;
14984     StopClocks();
14985     first.offeredDraw = second.offeredDraw = 0;
14986
14987     if (gameMode == PlayFromGameFile) {
14988         whiteTimeRemaining = timeRemaining[0][currentMove];
14989         blackTimeRemaining = timeRemaining[1][currentMove];
14990         DisplayTitle("");
14991     }
14992
14993     if (gameMode == MachinePlaysWhite ||
14994         gameMode == MachinePlaysBlack ||
14995         gameMode == TwoMachinesPlay ||
14996         gameMode == EndOfGame) {
14997         i = forwardMostMove;
14998         while (i > currentMove) {
14999             SendToProgram("undo\n", &first);
15000             i--;
15001         }
15002         if(!adjustedClock) {
15003         whiteTimeRemaining = timeRemaining[0][currentMove];
15004         blackTimeRemaining = timeRemaining[1][currentMove];
15005         DisplayBothClocks();
15006         }
15007         if (whiteFlag || blackFlag) {
15008             whiteFlag = blackFlag = 0;
15009         }
15010         DisplayTitle("");
15011     }
15012
15013     gameMode = EditGame;
15014     ModeHighlight();
15015     SetGameInfo();
15016 }
15017
15018
15019 void
15020 EditPositionEvent ()
15021 {
15022     if (gameMode == EditPosition) {
15023         EditGameEvent();
15024         return;
15025     }
15026
15027     EditGameEvent();
15028     if (gameMode != EditGame) return;
15029
15030     gameMode = EditPosition;
15031     ModeHighlight();
15032     SetGameInfo();
15033     if (currentMove > 0)
15034       CopyBoard(boards[0], boards[currentMove]);
15035
15036     blackPlaysFirst = !WhiteOnMove(currentMove);
15037     ResetClocks();
15038     currentMove = forwardMostMove = backwardMostMove = 0;
15039     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15040     DisplayMove(-1);
15041     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15042 }
15043
15044 void
15045 ExitAnalyzeMode ()
15046 {
15047     /* [DM] icsEngineAnalyze - possible call from other functions */
15048     if (appData.icsEngineAnalyze) {
15049         appData.icsEngineAnalyze = FALSE;
15050
15051         DisplayMessage("",_("Close ICS engine analyze..."));
15052     }
15053     if (first.analysisSupport && first.analyzing) {
15054       SendToBoth("exit\n");
15055       first.analyzing = second.analyzing = FALSE;
15056     }
15057     thinkOutput[0] = NULLCHAR;
15058 }
15059
15060 void
15061 EditPositionDone (Boolean fakeRights)
15062 {
15063     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15064
15065     startedFromSetupPosition = TRUE;
15066     InitChessProgram(&first, FALSE);
15067     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15068       boards[0][EP_STATUS] = EP_NONE;
15069       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15070       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15071         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15072         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15073       } else boards[0][CASTLING][2] = NoRights;
15074       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15075         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15076         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15077       } else boards[0][CASTLING][5] = NoRights;
15078       if(gameInfo.variant == VariantSChess) {
15079         int i;
15080         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15081           boards[0][VIRGIN][i] = 0;
15082           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15083           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15084         }
15085       }
15086     }
15087     SendToProgram("force\n", &first);
15088     if (blackPlaysFirst) {
15089         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15090         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15091         currentMove = forwardMostMove = backwardMostMove = 1;
15092         CopyBoard(boards[1], boards[0]);
15093     } else {
15094         currentMove = forwardMostMove = backwardMostMove = 0;
15095     }
15096     SendBoard(&first, forwardMostMove);
15097     if (appData.debugMode) {
15098         fprintf(debugFP, "EditPosDone\n");
15099     }
15100     DisplayTitle("");
15101     DisplayMessage("", "");
15102     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15103     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15104     gameMode = EditGame;
15105     ModeHighlight();
15106     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15107     ClearHighlights(); /* [AS] */
15108 }
15109
15110 /* Pause for `ms' milliseconds */
15111 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15112 void
15113 TimeDelay (long ms)
15114 {
15115     TimeMark m1, m2;
15116
15117     GetTimeMark(&m1);
15118     do {
15119         GetTimeMark(&m2);
15120     } while (SubtractTimeMarks(&m2, &m1) < ms);
15121 }
15122
15123 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15124 void
15125 SendMultiLineToICS (char *buf)
15126 {
15127     char temp[MSG_SIZ+1], *p;
15128     int len;
15129
15130     len = strlen(buf);
15131     if (len > MSG_SIZ)
15132       len = MSG_SIZ;
15133
15134     strncpy(temp, buf, len);
15135     temp[len] = 0;
15136
15137     p = temp;
15138     while (*p) {
15139         if (*p == '\n' || *p == '\r')
15140           *p = ' ';
15141         ++p;
15142     }
15143
15144     strcat(temp, "\n");
15145     SendToICS(temp);
15146     SendToPlayer(temp, strlen(temp));
15147 }
15148
15149 void
15150 SetWhiteToPlayEvent ()
15151 {
15152     if (gameMode == EditPosition) {
15153         blackPlaysFirst = FALSE;
15154         DisplayBothClocks();    /* works because currentMove is 0 */
15155     } else if (gameMode == IcsExamining) {
15156         SendToICS(ics_prefix);
15157         SendToICS("tomove white\n");
15158     }
15159 }
15160
15161 void
15162 SetBlackToPlayEvent ()
15163 {
15164     if (gameMode == EditPosition) {
15165         blackPlaysFirst = TRUE;
15166         currentMove = 1;        /* kludge */
15167         DisplayBothClocks();
15168         currentMove = 0;
15169     } else if (gameMode == IcsExamining) {
15170         SendToICS(ics_prefix);
15171         SendToICS("tomove black\n");
15172     }
15173 }
15174
15175 void
15176 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15177 {
15178     char buf[MSG_SIZ];
15179     ChessSquare piece = boards[0][y][x];
15180     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15181     static int lastVariant;
15182
15183     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15184
15185     switch (selection) {
15186       case ClearBoard:
15187         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15188         MarkTargetSquares(1);
15189         CopyBoard(currentBoard, boards[0]);
15190         CopyBoard(menuBoard, initialPosition);
15191         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15192             SendToICS(ics_prefix);
15193             SendToICS("bsetup clear\n");
15194         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15195             SendToICS(ics_prefix);
15196             SendToICS("clearboard\n");
15197         } else {
15198             int nonEmpty = 0;
15199             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15200                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15201                 for (y = 0; y < BOARD_HEIGHT; y++) {
15202                     if (gameMode == IcsExamining) {
15203                         if (boards[currentMove][y][x] != EmptySquare) {
15204                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15205                                     AAA + x, ONE + y);
15206                             SendToICS(buf);
15207                         }
15208                     } else if(boards[0][y][x] != DarkSquare) {
15209                         if(boards[0][y][x] != p) nonEmpty++;
15210                         boards[0][y][x] = p;
15211                     }
15212                 }
15213             }
15214             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15215                 int r;
15216                 for(r = 0; r < BOARD_HEIGHT; r++) {
15217                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15218                     ChessSquare p = menuBoard[r][x];
15219                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15220                   }
15221                 }
15222                 DisplayMessage("Clicking clock again restores position", "");
15223                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15224                 if(!nonEmpty) { // asked to clear an empty board
15225                     CopyBoard(boards[0], menuBoard);
15226                 } else
15227                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15228                     CopyBoard(boards[0], initialPosition);
15229                 } else
15230                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15231                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15232                     CopyBoard(boards[0], erasedBoard);
15233                 } else
15234                     CopyBoard(erasedBoard, currentBoard);
15235
15236             }
15237         }
15238         if (gameMode == EditPosition) {
15239             DrawPosition(FALSE, boards[0]);
15240         }
15241         break;
15242
15243       case WhitePlay:
15244         SetWhiteToPlayEvent();
15245         break;
15246
15247       case BlackPlay:
15248         SetBlackToPlayEvent();
15249         break;
15250
15251       case EmptySquare:
15252         if (gameMode == IcsExamining) {
15253             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15254             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15255             SendToICS(buf);
15256         } else {
15257             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15258                 if(x == BOARD_LEFT-2) {
15259                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15260                     boards[0][y][1] = 0;
15261                 } else
15262                 if(x == BOARD_RGHT+1) {
15263                     if(y >= gameInfo.holdingsSize) break;
15264                     boards[0][y][BOARD_WIDTH-2] = 0;
15265                 } else break;
15266             }
15267             boards[0][y][x] = EmptySquare;
15268             DrawPosition(FALSE, boards[0]);
15269         }
15270         break;
15271
15272       case PromotePiece:
15273         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15274            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15275             selection = (ChessSquare) (PROMOTED piece);
15276         } else if(piece == EmptySquare) selection = WhiteSilver;
15277         else selection = (ChessSquare)((int)piece - 1);
15278         goto defaultlabel;
15279
15280       case DemotePiece:
15281         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15282            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15283             selection = (ChessSquare) (DEMOTED piece);
15284         } else if(piece == EmptySquare) selection = BlackSilver;
15285         else selection = (ChessSquare)((int)piece + 1);
15286         goto defaultlabel;
15287
15288       case WhiteQueen:
15289       case BlackQueen:
15290         if(gameInfo.variant == VariantShatranj ||
15291            gameInfo.variant == VariantXiangqi  ||
15292            gameInfo.variant == VariantCourier  ||
15293            gameInfo.variant == VariantASEAN    ||
15294            gameInfo.variant == VariantMakruk     )
15295             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15296         goto defaultlabel;
15297
15298       case WhiteKing:
15299       case BlackKing:
15300         if(gameInfo.variant == VariantXiangqi)
15301             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15302         if(gameInfo.variant == VariantKnightmate)
15303             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15304       default:
15305         defaultlabel:
15306         if (gameMode == IcsExamining) {
15307             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15308             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15309                      PieceToChar(selection), AAA + x, ONE + y);
15310             SendToICS(buf);
15311         } else {
15312             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15313                 int n;
15314                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15315                     n = PieceToNumber(selection - BlackPawn);
15316                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15317                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15318                     boards[0][BOARD_HEIGHT-1-n][1]++;
15319                 } else
15320                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15321                     n = PieceToNumber(selection);
15322                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15323                     boards[0][n][BOARD_WIDTH-1] = selection;
15324                     boards[0][n][BOARD_WIDTH-2]++;
15325                 }
15326             } else
15327             boards[0][y][x] = selection;
15328             DrawPosition(TRUE, boards[0]);
15329             ClearHighlights();
15330             fromX = fromY = -1;
15331         }
15332         break;
15333     }
15334 }
15335
15336
15337 void
15338 DropMenuEvent (ChessSquare selection, int x, int y)
15339 {
15340     ChessMove moveType;
15341
15342     switch (gameMode) {
15343       case IcsPlayingWhite:
15344       case MachinePlaysBlack:
15345         if (!WhiteOnMove(currentMove)) {
15346             DisplayMoveError(_("It is Black's turn"));
15347             return;
15348         }
15349         moveType = WhiteDrop;
15350         break;
15351       case IcsPlayingBlack:
15352       case MachinePlaysWhite:
15353         if (WhiteOnMove(currentMove)) {
15354             DisplayMoveError(_("It is White's turn"));
15355             return;
15356         }
15357         moveType = BlackDrop;
15358         break;
15359       case EditGame:
15360         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15361         break;
15362       default:
15363         return;
15364     }
15365
15366     if (moveType == BlackDrop && selection < BlackPawn) {
15367       selection = (ChessSquare) ((int) selection
15368                                  + (int) BlackPawn - (int) WhitePawn);
15369     }
15370     if (boards[currentMove][y][x] != EmptySquare) {
15371         DisplayMoveError(_("That square is occupied"));
15372         return;
15373     }
15374
15375     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15376 }
15377
15378 void
15379 AcceptEvent ()
15380 {
15381     /* Accept a pending offer of any kind from opponent */
15382
15383     if (appData.icsActive) {
15384         SendToICS(ics_prefix);
15385         SendToICS("accept\n");
15386     } else if (cmailMsgLoaded) {
15387         if (currentMove == cmailOldMove &&
15388             commentList[cmailOldMove] != NULL &&
15389             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15390                    "Black offers a draw" : "White offers a draw")) {
15391             TruncateGame();
15392             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15393             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15394         } else {
15395             DisplayError(_("There is no pending offer on this move"), 0);
15396             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15397         }
15398     } else {
15399         /* Not used for offers from chess program */
15400     }
15401 }
15402
15403 void
15404 DeclineEvent ()
15405 {
15406     /* Decline a pending offer of any kind from opponent */
15407
15408     if (appData.icsActive) {
15409         SendToICS(ics_prefix);
15410         SendToICS("decline\n");
15411     } else if (cmailMsgLoaded) {
15412         if (currentMove == cmailOldMove &&
15413             commentList[cmailOldMove] != NULL &&
15414             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15415                    "Black offers a draw" : "White offers a draw")) {
15416 #ifdef NOTDEF
15417             AppendComment(cmailOldMove, "Draw declined", TRUE);
15418             DisplayComment(cmailOldMove - 1, "Draw declined");
15419 #endif /*NOTDEF*/
15420         } else {
15421             DisplayError(_("There is no pending offer on this move"), 0);
15422         }
15423     } else {
15424         /* Not used for offers from chess program */
15425     }
15426 }
15427
15428 void
15429 RematchEvent ()
15430 {
15431     /* Issue ICS rematch command */
15432     if (appData.icsActive) {
15433         SendToICS(ics_prefix);
15434         SendToICS("rematch\n");
15435     }
15436 }
15437
15438 void
15439 CallFlagEvent ()
15440 {
15441     /* Call your opponent's flag (claim a win on time) */
15442     if (appData.icsActive) {
15443         SendToICS(ics_prefix);
15444         SendToICS("flag\n");
15445     } else {
15446         switch (gameMode) {
15447           default:
15448             return;
15449           case MachinePlaysWhite:
15450             if (whiteFlag) {
15451                 if (blackFlag)
15452                   GameEnds(GameIsDrawn, "Both players ran out of time",
15453                            GE_PLAYER);
15454                 else
15455                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15456             } else {
15457                 DisplayError(_("Your opponent is not out of time"), 0);
15458             }
15459             break;
15460           case MachinePlaysBlack:
15461             if (blackFlag) {
15462                 if (whiteFlag)
15463                   GameEnds(GameIsDrawn, "Both players ran out of time",
15464                            GE_PLAYER);
15465                 else
15466                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15467             } else {
15468                 DisplayError(_("Your opponent is not out of time"), 0);
15469             }
15470             break;
15471         }
15472     }
15473 }
15474
15475 void
15476 ClockClick (int which)
15477 {       // [HGM] code moved to back-end from winboard.c
15478         if(which) { // black clock
15479           if (gameMode == EditPosition || gameMode == IcsExamining) {
15480             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15481             SetBlackToPlayEvent();
15482           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15483                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15484           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15485           } else if (shiftKey) {
15486             AdjustClock(which, -1);
15487           } else if (gameMode == IcsPlayingWhite ||
15488                      gameMode == MachinePlaysBlack) {
15489             CallFlagEvent();
15490           }
15491         } else { // white clock
15492           if (gameMode == EditPosition || gameMode == IcsExamining) {
15493             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15494             SetWhiteToPlayEvent();
15495           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15496                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15497           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15498           } else if (shiftKey) {
15499             AdjustClock(which, -1);
15500           } else if (gameMode == IcsPlayingBlack ||
15501                    gameMode == MachinePlaysWhite) {
15502             CallFlagEvent();
15503           }
15504         }
15505 }
15506
15507 void
15508 DrawEvent ()
15509 {
15510     /* Offer draw or accept pending draw offer from opponent */
15511
15512     if (appData.icsActive) {
15513         /* Note: tournament rules require draw offers to be
15514            made after you make your move but before you punch
15515            your clock.  Currently ICS doesn't let you do that;
15516            instead, you immediately punch your clock after making
15517            a move, but you can offer a draw at any time. */
15518
15519         SendToICS(ics_prefix);
15520         SendToICS("draw\n");
15521         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15522     } else if (cmailMsgLoaded) {
15523         if (currentMove == cmailOldMove &&
15524             commentList[cmailOldMove] != NULL &&
15525             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15526                    "Black offers a draw" : "White offers a draw")) {
15527             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15529         } else if (currentMove == cmailOldMove + 1) {
15530             char *offer = WhiteOnMove(cmailOldMove) ?
15531               "White offers a draw" : "Black offers a draw";
15532             AppendComment(currentMove, offer, TRUE);
15533             DisplayComment(currentMove - 1, offer);
15534             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15535         } else {
15536             DisplayError(_("You must make your move before offering a draw"), 0);
15537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15538         }
15539     } else if (first.offeredDraw) {
15540         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15541     } else {
15542         if (first.sendDrawOffers) {
15543             SendToProgram("draw\n", &first);
15544             userOfferedDraw = TRUE;
15545         }
15546     }
15547 }
15548
15549 void
15550 AdjournEvent ()
15551 {
15552     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15553
15554     if (appData.icsActive) {
15555         SendToICS(ics_prefix);
15556         SendToICS("adjourn\n");
15557     } else {
15558         /* Currently GNU Chess doesn't offer or accept Adjourns */
15559     }
15560 }
15561
15562
15563 void
15564 AbortEvent ()
15565 {
15566     /* Offer Abort or accept pending Abort offer from opponent */
15567
15568     if (appData.icsActive) {
15569         SendToICS(ics_prefix);
15570         SendToICS("abort\n");
15571     } else {
15572         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15573     }
15574 }
15575
15576 void
15577 ResignEvent ()
15578 {
15579     /* Resign.  You can do this even if it's not your turn. */
15580
15581     if (appData.icsActive) {
15582         SendToICS(ics_prefix);
15583         SendToICS("resign\n");
15584     } else {
15585         switch (gameMode) {
15586           case MachinePlaysWhite:
15587             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15588             break;
15589           case MachinePlaysBlack:
15590             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15591             break;
15592           case EditGame:
15593             if (cmailMsgLoaded) {
15594                 TruncateGame();
15595                 if (WhiteOnMove(cmailOldMove)) {
15596                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15597                 } else {
15598                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15599                 }
15600                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15601             }
15602             break;
15603           default:
15604             break;
15605         }
15606     }
15607 }
15608
15609
15610 void
15611 StopObservingEvent ()
15612 {
15613     /* Stop observing current games */
15614     SendToICS(ics_prefix);
15615     SendToICS("unobserve\n");
15616 }
15617
15618 void
15619 StopExaminingEvent ()
15620 {
15621     /* Stop observing current game */
15622     SendToICS(ics_prefix);
15623     SendToICS("unexamine\n");
15624 }
15625
15626 void
15627 ForwardInner (int target)
15628 {
15629     int limit; int oldSeekGraphUp = seekGraphUp;
15630
15631     if (appData.debugMode)
15632         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15633                 target, currentMove, forwardMostMove);
15634
15635     if (gameMode == EditPosition)
15636       return;
15637
15638     seekGraphUp = FALSE;
15639     MarkTargetSquares(1);
15640     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15641
15642     if (gameMode == PlayFromGameFile && !pausing)
15643       PauseEvent();
15644
15645     if (gameMode == IcsExamining && pausing)
15646       limit = pauseExamForwardMostMove;
15647     else
15648       limit = forwardMostMove;
15649
15650     if (target > limit) target = limit;
15651
15652     if (target > 0 && moveList[target - 1][0]) {
15653         int fromX, fromY, toX, toY;
15654         toX = moveList[target - 1][2] - AAA;
15655         toY = moveList[target - 1][3] - ONE;
15656         if (moveList[target - 1][1] == '@') {
15657             if (appData.highlightLastMove) {
15658                 SetHighlights(-1, -1, toX, toY);
15659             }
15660         } else {
15661             int viaX = moveList[target - 1][5] - AAA;
15662             int viaY = moveList[target - 1][6] - ONE;
15663             fromX = moveList[target - 1][0] - AAA;
15664             fromY = moveList[target - 1][1] - ONE;
15665             if (target == currentMove + 1) {
15666                 if(moveList[target - 1][4] == ';') { // multi-leg
15667                     ChessSquare piece = boards[currentMove][viaY][viaX];
15668                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15669                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15670                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15671                     boards[currentMove][viaY][viaX] = piece;
15672                 } else
15673                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15674             }
15675             if (appData.highlightLastMove) {
15676                 SetHighlights(fromX, fromY, toX, toY);
15677             }
15678         }
15679     }
15680     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15681         gameMode == Training || gameMode == PlayFromGameFile ||
15682         gameMode == AnalyzeFile) {
15683         while (currentMove < target) {
15684             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15685             SendMoveToProgram(currentMove++, &first);
15686         }
15687     } else {
15688         currentMove = target;
15689     }
15690
15691     if (gameMode == EditGame || gameMode == EndOfGame) {
15692         whiteTimeRemaining = timeRemaining[0][currentMove];
15693         blackTimeRemaining = timeRemaining[1][currentMove];
15694     }
15695     DisplayBothClocks();
15696     DisplayMove(currentMove - 1);
15697     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15698     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15699     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15700         DisplayComment(currentMove - 1, commentList[currentMove]);
15701     }
15702     ClearMap(); // [HGM] exclude: invalidate map
15703 }
15704
15705
15706 void
15707 ForwardEvent ()
15708 {
15709     if (gameMode == IcsExamining && !pausing) {
15710         SendToICS(ics_prefix);
15711         SendToICS("forward\n");
15712     } else {
15713         ForwardInner(currentMove + 1);
15714     }
15715 }
15716
15717 void
15718 ToEndEvent ()
15719 {
15720     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15721         /* to optimze, we temporarily turn off analysis mode while we feed
15722          * the remaining moves to the engine. Otherwise we get analysis output
15723          * after each move.
15724          */
15725         if (first.analysisSupport) {
15726           SendToProgram("exit\nforce\n", &first);
15727           first.analyzing = FALSE;
15728         }
15729     }
15730
15731     if (gameMode == IcsExamining && !pausing) {
15732         SendToICS(ics_prefix);
15733         SendToICS("forward 999999\n");
15734     } else {
15735         ForwardInner(forwardMostMove);
15736     }
15737
15738     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15739         /* we have fed all the moves, so reactivate analysis mode */
15740         SendToProgram("analyze\n", &first);
15741         first.analyzing = TRUE;
15742         /*first.maybeThinking = TRUE;*/
15743         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15744     }
15745 }
15746
15747 void
15748 BackwardInner (int target)
15749 {
15750     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15751
15752     if (appData.debugMode)
15753         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15754                 target, currentMove, forwardMostMove);
15755
15756     if (gameMode == EditPosition) return;
15757     seekGraphUp = FALSE;
15758     MarkTargetSquares(1);
15759     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15760     if (currentMove <= backwardMostMove) {
15761         ClearHighlights();
15762         DrawPosition(full_redraw, boards[currentMove]);
15763         return;
15764     }
15765     if (gameMode == PlayFromGameFile && !pausing)
15766       PauseEvent();
15767
15768     if (moveList[target][0]) {
15769         int fromX, fromY, toX, toY;
15770         toX = moveList[target][2] - AAA;
15771         toY = moveList[target][3] - ONE;
15772         if (moveList[target][1] == '@') {
15773             if (appData.highlightLastMove) {
15774                 SetHighlights(-1, -1, toX, toY);
15775             }
15776         } else {
15777             fromX = moveList[target][0] - AAA;
15778             fromY = moveList[target][1] - ONE;
15779             if (target == currentMove - 1) {
15780                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15781             }
15782             if (appData.highlightLastMove) {
15783                 SetHighlights(fromX, fromY, toX, toY);
15784             }
15785         }
15786     }
15787     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15788         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15789         while (currentMove > target) {
15790             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15791                 // null move cannot be undone. Reload program with move history before it.
15792                 int i;
15793                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15794                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15795                 }
15796                 SendBoard(&first, i);
15797               if(second.analyzing) SendBoard(&second, i);
15798                 for(currentMove=i; currentMove<target; currentMove++) {
15799                     SendMoveToProgram(currentMove, &first);
15800                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15801                 }
15802                 break;
15803             }
15804             SendToBoth("undo\n");
15805             currentMove--;
15806         }
15807     } else {
15808         currentMove = target;
15809     }
15810
15811     if (gameMode == EditGame || gameMode == EndOfGame) {
15812         whiteTimeRemaining = timeRemaining[0][currentMove];
15813         blackTimeRemaining = timeRemaining[1][currentMove];
15814     }
15815     DisplayBothClocks();
15816     DisplayMove(currentMove - 1);
15817     DrawPosition(full_redraw, boards[currentMove]);
15818     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15819     // [HGM] PV info: routine tests if comment empty
15820     DisplayComment(currentMove - 1, commentList[currentMove]);
15821     ClearMap(); // [HGM] exclude: invalidate map
15822 }
15823
15824 void
15825 BackwardEvent ()
15826 {
15827     if (gameMode == IcsExamining && !pausing) {
15828         SendToICS(ics_prefix);
15829         SendToICS("backward\n");
15830     } else {
15831         BackwardInner(currentMove - 1);
15832     }
15833 }
15834
15835 void
15836 ToStartEvent ()
15837 {
15838     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15839         /* to optimize, we temporarily turn off analysis mode while we undo
15840          * all the moves. Otherwise we get analysis output after each undo.
15841          */
15842         if (first.analysisSupport) {
15843           SendToProgram("exit\nforce\n", &first);
15844           first.analyzing = FALSE;
15845         }
15846     }
15847
15848     if (gameMode == IcsExamining && !pausing) {
15849         SendToICS(ics_prefix);
15850         SendToICS("backward 999999\n");
15851     } else {
15852         BackwardInner(backwardMostMove);
15853     }
15854
15855     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15856         /* we have fed all the moves, so reactivate analysis mode */
15857         SendToProgram("analyze\n", &first);
15858         first.analyzing = TRUE;
15859         /*first.maybeThinking = TRUE;*/
15860         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15861     }
15862 }
15863
15864 void
15865 ToNrEvent (int to)
15866 {
15867   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15868   if (to >= forwardMostMove) to = forwardMostMove;
15869   if (to <= backwardMostMove) to = backwardMostMove;
15870   if (to < currentMove) {
15871     BackwardInner(to);
15872   } else {
15873     ForwardInner(to);
15874   }
15875 }
15876
15877 void
15878 RevertEvent (Boolean annotate)
15879 {
15880     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15881         return;
15882     }
15883     if (gameMode != IcsExamining) {
15884         DisplayError(_("You are not examining a game"), 0);
15885         return;
15886     }
15887     if (pausing) {
15888         DisplayError(_("You can't revert while pausing"), 0);
15889         return;
15890     }
15891     SendToICS(ics_prefix);
15892     SendToICS("revert\n");
15893 }
15894
15895 void
15896 RetractMoveEvent ()
15897 {
15898     switch (gameMode) {
15899       case MachinePlaysWhite:
15900       case MachinePlaysBlack:
15901         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15902             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15903             return;
15904         }
15905         if (forwardMostMove < 2) return;
15906         currentMove = forwardMostMove = forwardMostMove - 2;
15907         whiteTimeRemaining = timeRemaining[0][currentMove];
15908         blackTimeRemaining = timeRemaining[1][currentMove];
15909         DisplayBothClocks();
15910         DisplayMove(currentMove - 1);
15911         ClearHighlights();/*!! could figure this out*/
15912         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15913         SendToProgram("remove\n", &first);
15914         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15915         break;
15916
15917       case BeginningOfGame:
15918       default:
15919         break;
15920
15921       case IcsPlayingWhite:
15922       case IcsPlayingBlack:
15923         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15924             SendToICS(ics_prefix);
15925             SendToICS("takeback 2\n");
15926         } else {
15927             SendToICS(ics_prefix);
15928             SendToICS("takeback 1\n");
15929         }
15930         break;
15931     }
15932 }
15933
15934 void
15935 MoveNowEvent ()
15936 {
15937     ChessProgramState *cps;
15938
15939     switch (gameMode) {
15940       case MachinePlaysWhite:
15941         if (!WhiteOnMove(forwardMostMove)) {
15942             DisplayError(_("It is your turn"), 0);
15943             return;
15944         }
15945         cps = &first;
15946         break;
15947       case MachinePlaysBlack:
15948         if (WhiteOnMove(forwardMostMove)) {
15949             DisplayError(_("It is your turn"), 0);
15950             return;
15951         }
15952         cps = &first;
15953         break;
15954       case TwoMachinesPlay:
15955         if (WhiteOnMove(forwardMostMove) ==
15956             (first.twoMachinesColor[0] == 'w')) {
15957             cps = &first;
15958         } else {
15959             cps = &second;
15960         }
15961         break;
15962       case BeginningOfGame:
15963       default:
15964         return;
15965     }
15966     SendToProgram("?\n", cps);
15967 }
15968
15969 void
15970 TruncateGameEvent ()
15971 {
15972     EditGameEvent();
15973     if (gameMode != EditGame) return;
15974     TruncateGame();
15975 }
15976
15977 void
15978 TruncateGame ()
15979 {
15980     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15981     if (forwardMostMove > currentMove) {
15982         if (gameInfo.resultDetails != NULL) {
15983             free(gameInfo.resultDetails);
15984             gameInfo.resultDetails = NULL;
15985             gameInfo.result = GameUnfinished;
15986         }
15987         forwardMostMove = currentMove;
15988         HistorySet(parseList, backwardMostMove, forwardMostMove,
15989                    currentMove-1);
15990     }
15991 }
15992
15993 void
15994 HintEvent ()
15995 {
15996     if (appData.noChessProgram) return;
15997     switch (gameMode) {
15998       case MachinePlaysWhite:
15999         if (WhiteOnMove(forwardMostMove)) {
16000             DisplayError(_("Wait until your turn."), 0);
16001             return;
16002         }
16003         break;
16004       case BeginningOfGame:
16005       case MachinePlaysBlack:
16006         if (!WhiteOnMove(forwardMostMove)) {
16007             DisplayError(_("Wait until your turn."), 0);
16008             return;
16009         }
16010         break;
16011       default:
16012         DisplayError(_("No hint available"), 0);
16013         return;
16014     }
16015     SendToProgram("hint\n", &first);
16016     hintRequested = TRUE;
16017 }
16018
16019 int
16020 SaveSelected (FILE *g, int dummy, char *dummy2)
16021 {
16022     ListGame * lg = (ListGame *) gameList.head;
16023     int nItem, cnt=0;
16024     FILE *f;
16025
16026     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16027         DisplayError(_("Game list not loaded or empty"), 0);
16028         return 0;
16029     }
16030
16031     creatingBook = TRUE; // suppresses stuff during load game
16032
16033     /* Get list size */
16034     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16035         if(lg->position >= 0) { // selected?
16036             LoadGame(f, nItem, "", TRUE);
16037             SaveGamePGN2(g); // leaves g open
16038             cnt++; DoEvents();
16039         }
16040         lg = (ListGame *) lg->node.succ;
16041     }
16042
16043     fclose(g);
16044     creatingBook = FALSE;
16045
16046     return cnt;
16047 }
16048
16049 void
16050 CreateBookEvent ()
16051 {
16052     ListGame * lg = (ListGame *) gameList.head;
16053     FILE *f, *g;
16054     int nItem;
16055     static int secondTime = FALSE;
16056
16057     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16058         DisplayError(_("Game list not loaded or empty"), 0);
16059         return;
16060     }
16061
16062     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16063         fclose(g);
16064         secondTime++;
16065         DisplayNote(_("Book file exists! Try again for overwrite."));
16066         return;
16067     }
16068
16069     creatingBook = TRUE;
16070     secondTime = FALSE;
16071
16072     /* Get list size */
16073     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16074         if(lg->position >= 0) {
16075             LoadGame(f, nItem, "", TRUE);
16076             AddGameToBook(TRUE);
16077             DoEvents();
16078         }
16079         lg = (ListGame *) lg->node.succ;
16080     }
16081
16082     creatingBook = FALSE;
16083     FlushBook();
16084 }
16085
16086 void
16087 BookEvent ()
16088 {
16089     if (appData.noChessProgram) return;
16090     switch (gameMode) {
16091       case MachinePlaysWhite:
16092         if (WhiteOnMove(forwardMostMove)) {
16093             DisplayError(_("Wait until your turn."), 0);
16094             return;
16095         }
16096         break;
16097       case BeginningOfGame:
16098       case MachinePlaysBlack:
16099         if (!WhiteOnMove(forwardMostMove)) {
16100             DisplayError(_("Wait until your turn."), 0);
16101             return;
16102         }
16103         break;
16104       case EditPosition:
16105         EditPositionDone(TRUE);
16106         break;
16107       case TwoMachinesPlay:
16108         return;
16109       default:
16110         break;
16111     }
16112     SendToProgram("bk\n", &first);
16113     bookOutput[0] = NULLCHAR;
16114     bookRequested = TRUE;
16115 }
16116
16117 void
16118 AboutGameEvent ()
16119 {
16120     char *tags = PGNTags(&gameInfo);
16121     TagsPopUp(tags, CmailMsg());
16122     free(tags);
16123 }
16124
16125 /* end button procedures */
16126
16127 void
16128 PrintPosition (FILE *fp, int move)
16129 {
16130     int i, j;
16131
16132     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16133         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16134             char c = PieceToChar(boards[move][i][j]);
16135             fputc(c == 'x' ? '.' : c, fp);
16136             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16137         }
16138     }
16139     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16140       fprintf(fp, "white to play\n");
16141     else
16142       fprintf(fp, "black to play\n");
16143 }
16144
16145 void
16146 PrintOpponents (FILE *fp)
16147 {
16148     if (gameInfo.white != NULL) {
16149         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16150     } else {
16151         fprintf(fp, "\n");
16152     }
16153 }
16154
16155 /* Find last component of program's own name, using some heuristics */
16156 void
16157 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16158 {
16159     char *p, *q, c;
16160     int local = (strcmp(host, "localhost") == 0);
16161     while (!local && (p = strchr(prog, ';')) != NULL) {
16162         p++;
16163         while (*p == ' ') p++;
16164         prog = p;
16165     }
16166     if (*prog == '"' || *prog == '\'') {
16167         q = strchr(prog + 1, *prog);
16168     } else {
16169         q = strchr(prog, ' ');
16170     }
16171     if (q == NULL) q = prog + strlen(prog);
16172     p = q;
16173     while (p >= prog && *p != '/' && *p != '\\') p--;
16174     p++;
16175     if(p == prog && *p == '"') p++;
16176     c = *q; *q = 0;
16177     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16178     memcpy(buf, p, q - p);
16179     buf[q - p] = NULLCHAR;
16180     if (!local) {
16181         strcat(buf, "@");
16182         strcat(buf, host);
16183     }
16184 }
16185
16186 char *
16187 TimeControlTagValue ()
16188 {
16189     char buf[MSG_SIZ];
16190     if (!appData.clockMode) {
16191       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16192     } else if (movesPerSession > 0) {
16193       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16194     } else if (timeIncrement == 0) {
16195       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16196     } else {
16197       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16198     }
16199     return StrSave(buf);
16200 }
16201
16202 void
16203 SetGameInfo ()
16204 {
16205     /* This routine is used only for certain modes */
16206     VariantClass v = gameInfo.variant;
16207     ChessMove r = GameUnfinished;
16208     char *p = NULL;
16209
16210     if(keepInfo) return;
16211
16212     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16213         r = gameInfo.result;
16214         p = gameInfo.resultDetails;
16215         gameInfo.resultDetails = NULL;
16216     }
16217     ClearGameInfo(&gameInfo);
16218     gameInfo.variant = v;
16219
16220     switch (gameMode) {
16221       case MachinePlaysWhite:
16222         gameInfo.event = StrSave( appData.pgnEventHeader );
16223         gameInfo.site = StrSave(HostName());
16224         gameInfo.date = PGNDate();
16225         gameInfo.round = StrSave("-");
16226         gameInfo.white = StrSave(first.tidy);
16227         gameInfo.black = StrSave(UserName());
16228         gameInfo.timeControl = TimeControlTagValue();
16229         break;
16230
16231       case MachinePlaysBlack:
16232         gameInfo.event = StrSave( appData.pgnEventHeader );
16233         gameInfo.site = StrSave(HostName());
16234         gameInfo.date = PGNDate();
16235         gameInfo.round = StrSave("-");
16236         gameInfo.white = StrSave(UserName());
16237         gameInfo.black = StrSave(first.tidy);
16238         gameInfo.timeControl = TimeControlTagValue();
16239         break;
16240
16241       case TwoMachinesPlay:
16242         gameInfo.event = StrSave( appData.pgnEventHeader );
16243         gameInfo.site = StrSave(HostName());
16244         gameInfo.date = PGNDate();
16245         if (roundNr > 0) {
16246             char buf[MSG_SIZ];
16247             snprintf(buf, MSG_SIZ, "%d", roundNr);
16248             gameInfo.round = StrSave(buf);
16249         } else {
16250             gameInfo.round = StrSave("-");
16251         }
16252         if (first.twoMachinesColor[0] == 'w') {
16253             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16254             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16255         } else {
16256             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16257             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16258         }
16259         gameInfo.timeControl = TimeControlTagValue();
16260         break;
16261
16262       case EditGame:
16263         gameInfo.event = StrSave("Edited game");
16264         gameInfo.site = StrSave(HostName());
16265         gameInfo.date = PGNDate();
16266         gameInfo.round = StrSave("-");
16267         gameInfo.white = StrSave("-");
16268         gameInfo.black = StrSave("-");
16269         gameInfo.result = r;
16270         gameInfo.resultDetails = p;
16271         break;
16272
16273       case EditPosition:
16274         gameInfo.event = StrSave("Edited position");
16275         gameInfo.site = StrSave(HostName());
16276         gameInfo.date = PGNDate();
16277         gameInfo.round = StrSave("-");
16278         gameInfo.white = StrSave("-");
16279         gameInfo.black = StrSave("-");
16280         break;
16281
16282       case IcsPlayingWhite:
16283       case IcsPlayingBlack:
16284       case IcsObserving:
16285       case IcsExamining:
16286         break;
16287
16288       case PlayFromGameFile:
16289         gameInfo.event = StrSave("Game from non-PGN file");
16290         gameInfo.site = StrSave(HostName());
16291         gameInfo.date = PGNDate();
16292         gameInfo.round = StrSave("-");
16293         gameInfo.white = StrSave("?");
16294         gameInfo.black = StrSave("?");
16295         break;
16296
16297       default:
16298         break;
16299     }
16300 }
16301
16302 void
16303 ReplaceComment (int index, char *text)
16304 {
16305     int len;
16306     char *p;
16307     float score;
16308
16309     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16310        pvInfoList[index-1].depth == len &&
16311        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16312        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16313     while (*text == '\n') text++;
16314     len = strlen(text);
16315     while (len > 0 && text[len - 1] == '\n') len--;
16316
16317     if (commentList[index] != NULL)
16318       free(commentList[index]);
16319
16320     if (len == 0) {
16321         commentList[index] = NULL;
16322         return;
16323     }
16324   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16325       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16326       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16327     commentList[index] = (char *) malloc(len + 2);
16328     strncpy(commentList[index], text, len);
16329     commentList[index][len] = '\n';
16330     commentList[index][len + 1] = NULLCHAR;
16331   } else {
16332     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16333     char *p;
16334     commentList[index] = (char *) malloc(len + 7);
16335     safeStrCpy(commentList[index], "{\n", 3);
16336     safeStrCpy(commentList[index]+2, text, len+1);
16337     commentList[index][len+2] = NULLCHAR;
16338     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16339     strcat(commentList[index], "\n}\n");
16340   }
16341 }
16342
16343 void
16344 CrushCRs (char *text)
16345 {
16346   char *p = text;
16347   char *q = text;
16348   char ch;
16349
16350   do {
16351     ch = *p++;
16352     if (ch == '\r') continue;
16353     *q++ = ch;
16354   } while (ch != '\0');
16355 }
16356
16357 void
16358 AppendComment (int index, char *text, Boolean addBraces)
16359 /* addBraces  tells if we should add {} */
16360 {
16361     int oldlen, len;
16362     char *old;
16363
16364 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16365     if(addBraces == 3) addBraces = 0; else // force appending literally
16366     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16367
16368     CrushCRs(text);
16369     while (*text == '\n') text++;
16370     len = strlen(text);
16371     while (len > 0 && text[len - 1] == '\n') len--;
16372     text[len] = NULLCHAR;
16373
16374     if (len == 0) return;
16375
16376     if (commentList[index] != NULL) {
16377       Boolean addClosingBrace = addBraces;
16378         old = commentList[index];
16379         oldlen = strlen(old);
16380         while(commentList[index][oldlen-1] ==  '\n')
16381           commentList[index][--oldlen] = NULLCHAR;
16382         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16383         safeStrCpy(commentList[index], old, oldlen + len + 6);
16384         free(old);
16385         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16386         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16387           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16388           while (*text == '\n') { text++; len--; }
16389           commentList[index][--oldlen] = NULLCHAR;
16390       }
16391         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16392         else          strcat(commentList[index], "\n");
16393         strcat(commentList[index], text);
16394         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16395         else          strcat(commentList[index], "\n");
16396     } else {
16397         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16398         if(addBraces)
16399           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16400         else commentList[index][0] = NULLCHAR;
16401         strcat(commentList[index], text);
16402         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16403         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16404     }
16405 }
16406
16407 static char *
16408 FindStr (char * text, char * sub_text)
16409 {
16410     char * result = strstr( text, sub_text );
16411
16412     if( result != NULL ) {
16413         result += strlen( sub_text );
16414     }
16415
16416     return result;
16417 }
16418
16419 /* [AS] Try to extract PV info from PGN comment */
16420 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16421 char *
16422 GetInfoFromComment (int index, char * text)
16423 {
16424     char * sep = text, *p;
16425
16426     if( text != NULL && index > 0 ) {
16427         int score = 0;
16428         int depth = 0;
16429         int time = -1, sec = 0, deci;
16430         char * s_eval = FindStr( text, "[%eval " );
16431         char * s_emt = FindStr( text, "[%emt " );
16432 #if 0
16433         if( s_eval != NULL || s_emt != NULL ) {
16434 #else
16435         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16436 #endif
16437             /* New style */
16438             char delim;
16439
16440             if( s_eval != NULL ) {
16441                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16442                     return text;
16443                 }
16444
16445                 if( delim != ']' ) {
16446                     return text;
16447                 }
16448             }
16449
16450             if( s_emt != NULL ) {
16451             }
16452                 return text;
16453         }
16454         else {
16455             /* We expect something like: [+|-]nnn.nn/dd */
16456             int score_lo = 0;
16457
16458             if(*text != '{') return text; // [HGM] braces: must be normal comment
16459
16460             sep = strchr( text, '/' );
16461             if( sep == NULL || sep < (text+4) ) {
16462                 return text;
16463             }
16464
16465             p = text;
16466             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16467             if(p[1] == '(') { // comment starts with PV
16468                p = strchr(p, ')'); // locate end of PV
16469                if(p == NULL || sep < p+5) return text;
16470                // at this point we have something like "{(.*) +0.23/6 ..."
16471                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16472                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16473                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16474             }
16475             time = -1; sec = -1; deci = -1;
16476             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16477                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16478                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16479                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16480                 return text;
16481             }
16482
16483             if( score_lo < 0 || score_lo >= 100 ) {
16484                 return text;
16485             }
16486
16487             if(sec >= 0) time = 600*time + 10*sec; else
16488             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16489
16490             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16491
16492             /* [HGM] PV time: now locate end of PV info */
16493             while( *++sep >= '0' && *sep <= '9'); // strip depth
16494             if(time >= 0)
16495             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16496             if(sec >= 0)
16497             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16498             if(deci >= 0)
16499             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16500             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16501         }
16502
16503         if( depth <= 0 ) {
16504             return text;
16505         }
16506
16507         if( time < 0 ) {
16508             time = -1;
16509         }
16510
16511         pvInfoList[index-1].depth = depth;
16512         pvInfoList[index-1].score = score;
16513         pvInfoList[index-1].time  = 10*time; // centi-sec
16514         if(*sep == '}') *sep = 0; else *--sep = '{';
16515         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16516     }
16517     return sep;
16518 }
16519
16520 void
16521 SendToProgram (char *message, ChessProgramState *cps)
16522 {
16523     int count, outCount, error;
16524     char buf[MSG_SIZ];
16525
16526     if (cps->pr == NoProc) return;
16527     Attention(cps);
16528
16529     if (appData.debugMode) {
16530         TimeMark now;
16531         GetTimeMark(&now);
16532         fprintf(debugFP, "%ld >%-6s: %s",
16533                 SubtractTimeMarks(&now, &programStartTime),
16534                 cps->which, message);
16535         if(serverFP)
16536             fprintf(serverFP, "%ld >%-6s: %s",
16537                 SubtractTimeMarks(&now, &programStartTime),
16538                 cps->which, message), fflush(serverFP);
16539     }
16540
16541     count = strlen(message);
16542     outCount = OutputToProcess(cps->pr, message, count, &error);
16543     if (outCount < count && !exiting
16544                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16545       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16546       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16547         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16548             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16549                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16550                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16551                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16552             } else {
16553                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16554                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16555                 gameInfo.result = res;
16556             }
16557             gameInfo.resultDetails = StrSave(buf);
16558         }
16559         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16560         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16561     }
16562 }
16563
16564 void
16565 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16566 {
16567     char *end_str;
16568     char buf[MSG_SIZ];
16569     ChessProgramState *cps = (ChessProgramState *)closure;
16570
16571     if (isr != cps->isr) return; /* Killed intentionally */
16572     if (count <= 0) {
16573         if (count == 0) {
16574             RemoveInputSource(cps->isr);
16575             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16576                     _(cps->which), cps->program);
16577             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16578             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16579                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16580                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16581                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16582                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16583                 } else {
16584                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16585                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16586                     gameInfo.result = res;
16587                 }
16588                 gameInfo.resultDetails = StrSave(buf);
16589             }
16590             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16591             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16592         } else {
16593             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16594                     _(cps->which), cps->program);
16595             RemoveInputSource(cps->isr);
16596
16597             /* [AS] Program is misbehaving badly... kill it */
16598             if( count == -2 ) {
16599                 DestroyChildProcess( cps->pr, 9 );
16600                 cps->pr = NoProc;
16601             }
16602
16603             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16604         }
16605         return;
16606     }
16607
16608     if ((end_str = strchr(message, '\r')) != NULL)
16609       *end_str = NULLCHAR;
16610     if ((end_str = strchr(message, '\n')) != NULL)
16611       *end_str = NULLCHAR;
16612
16613     if (appData.debugMode) {
16614         TimeMark now; int print = 1;
16615         char *quote = ""; char c; int i;
16616
16617         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16618                 char start = message[0];
16619                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16620                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16621                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16622                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16623                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16624                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16625                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16626                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16627                    sscanf(message, "hint: %c", &c)!=1 &&
16628                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16629                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16630                     print = (appData.engineComments >= 2);
16631                 }
16632                 message[0] = start; // restore original message
16633         }
16634         if(print) {
16635                 GetTimeMark(&now);
16636                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16637                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16638                         quote,
16639                         message);
16640                 if(serverFP)
16641                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16642                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16643                         quote,
16644                         message), fflush(serverFP);
16645         }
16646     }
16647
16648     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16649     if (appData.icsEngineAnalyze) {
16650         if (strstr(message, "whisper") != NULL ||
16651              strstr(message, "kibitz") != NULL ||
16652             strstr(message, "tellics") != NULL) return;
16653     }
16654
16655     HandleMachineMove(message, cps);
16656 }
16657
16658
16659 void
16660 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16661 {
16662     char buf[MSG_SIZ];
16663     int seconds;
16664
16665     if( timeControl_2 > 0 ) {
16666         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16667             tc = timeControl_2;
16668         }
16669     }
16670     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16671     inc /= cps->timeOdds;
16672     st  /= cps->timeOdds;
16673
16674     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16675
16676     if (st > 0) {
16677       /* Set exact time per move, normally using st command */
16678       if (cps->stKludge) {
16679         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16680         seconds = st % 60;
16681         if (seconds == 0) {
16682           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16683         } else {
16684           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16685         }
16686       } else {
16687         snprintf(buf, MSG_SIZ, "st %d\n", st);
16688       }
16689     } else {
16690       /* Set conventional or incremental time control, using level command */
16691       if (seconds == 0) {
16692         /* Note old gnuchess bug -- minutes:seconds used to not work.
16693            Fixed in later versions, but still avoid :seconds
16694            when seconds is 0. */
16695         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16696       } else {
16697         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16698                  seconds, inc/1000.);
16699       }
16700     }
16701     SendToProgram(buf, cps);
16702
16703     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16704     /* Orthogonally, limit search to given depth */
16705     if (sd > 0) {
16706       if (cps->sdKludge) {
16707         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16708       } else {
16709         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16710       }
16711       SendToProgram(buf, cps);
16712     }
16713
16714     if(cps->nps >= 0) { /* [HGM] nps */
16715         if(cps->supportsNPS == FALSE)
16716           cps->nps = -1; // don't use if engine explicitly says not supported!
16717         else {
16718           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16719           SendToProgram(buf, cps);
16720         }
16721     }
16722 }
16723
16724 ChessProgramState *
16725 WhitePlayer ()
16726 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16727 {
16728     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16729        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16730         return &second;
16731     return &first;
16732 }
16733
16734 void
16735 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16736 {
16737     char message[MSG_SIZ];
16738     long time, otime;
16739
16740     /* Note: this routine must be called when the clocks are stopped
16741        or when they have *just* been set or switched; otherwise
16742        it will be off by the time since the current tick started.
16743     */
16744     if (machineWhite) {
16745         time = whiteTimeRemaining / 10;
16746         otime = blackTimeRemaining / 10;
16747     } else {
16748         time = blackTimeRemaining / 10;
16749         otime = whiteTimeRemaining / 10;
16750     }
16751     /* [HGM] translate opponent's time by time-odds factor */
16752     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16753
16754     if (time <= 0) time = 1;
16755     if (otime <= 0) otime = 1;
16756
16757     snprintf(message, MSG_SIZ, "time %ld\n", time);
16758     SendToProgram(message, cps);
16759
16760     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16761     SendToProgram(message, cps);
16762 }
16763
16764 char *
16765 EngineDefinedVariant (ChessProgramState *cps, int n)
16766 {   // return name of n-th unknown variant that engine supports
16767     static char buf[MSG_SIZ];
16768     char *p, *s = cps->variants;
16769     if(!s) return NULL;
16770     do { // parse string from variants feature
16771       VariantClass v;
16772         p = strchr(s, ',');
16773         if(p) *p = NULLCHAR;
16774       v = StringToVariant(s);
16775       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16776         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16777             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16778                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16779                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16780                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16781             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16782         }
16783         if(p) *p++ = ',';
16784         if(n < 0) return buf;
16785     } while(s = p);
16786     return NULL;
16787 }
16788
16789 int
16790 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16791 {
16792   char buf[MSG_SIZ];
16793   int len = strlen(name);
16794   int val;
16795
16796   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16797     (*p) += len + 1;
16798     sscanf(*p, "%d", &val);
16799     *loc = (val != 0);
16800     while (**p && **p != ' ')
16801       (*p)++;
16802     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16803     SendToProgram(buf, cps);
16804     return TRUE;
16805   }
16806   return FALSE;
16807 }
16808
16809 int
16810 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16811 {
16812   char buf[MSG_SIZ];
16813   int len = strlen(name);
16814   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16815     (*p) += len + 1;
16816     sscanf(*p, "%d", loc);
16817     while (**p && **p != ' ') (*p)++;
16818     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16819     SendToProgram(buf, cps);
16820     return TRUE;
16821   }
16822   return FALSE;
16823 }
16824
16825 int
16826 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16827 {
16828   char buf[MSG_SIZ];
16829   int len = strlen(name);
16830   if (strncmp((*p), name, len) == 0
16831       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16832     (*p) += len + 2;
16833     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16834     sscanf(*p, "%[^\"]", *loc);
16835     while (**p && **p != '\"') (*p)++;
16836     if (**p == '\"') (*p)++;
16837     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16838     SendToProgram(buf, cps);
16839     return TRUE;
16840   }
16841   return FALSE;
16842 }
16843
16844 int
16845 ParseOption (Option *opt, ChessProgramState *cps)
16846 // [HGM] options: process the string that defines an engine option, and determine
16847 // name, type, default value, and allowed value range
16848 {
16849         char *p, *q, buf[MSG_SIZ];
16850         int n, min = (-1)<<31, max = 1<<31, def;
16851
16852         if(p = strstr(opt->name, " -spin ")) {
16853             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16854             if(max < min) max = min; // enforce consistency
16855             if(def < min) def = min;
16856             if(def > max) def = max;
16857             opt->value = def;
16858             opt->min = min;
16859             opt->max = max;
16860             opt->type = Spin;
16861         } else if((p = strstr(opt->name, " -slider "))) {
16862             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16863             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16864             if(max < min) max = min; // enforce consistency
16865             if(def < min) def = min;
16866             if(def > max) def = max;
16867             opt->value = def;
16868             opt->min = min;
16869             opt->max = max;
16870             opt->type = Spin; // Slider;
16871         } else if((p = strstr(opt->name, " -string "))) {
16872             opt->textValue = p+9;
16873             opt->type = TextBox;
16874         } else if((p = strstr(opt->name, " -file "))) {
16875             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16876             opt->textValue = p+7;
16877             opt->type = FileName; // FileName;
16878         } else if((p = strstr(opt->name, " -path "))) {
16879             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16880             opt->textValue = p+7;
16881             opt->type = PathName; // PathName;
16882         } else if(p = strstr(opt->name, " -check ")) {
16883             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16884             opt->value = (def != 0);
16885             opt->type = CheckBox;
16886         } else if(p = strstr(opt->name, " -combo ")) {
16887             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16888             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16889             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16890             opt->value = n = 0;
16891             while(q = StrStr(q, " /// ")) {
16892                 n++; *q = 0;    // count choices, and null-terminate each of them
16893                 q += 5;
16894                 if(*q == '*') { // remember default, which is marked with * prefix
16895                     q++;
16896                     opt->value = n;
16897                 }
16898                 cps->comboList[cps->comboCnt++] = q;
16899             }
16900             cps->comboList[cps->comboCnt++] = NULL;
16901             opt->max = n + 1;
16902             opt->type = ComboBox;
16903         } else if(p = strstr(opt->name, " -button")) {
16904             opt->type = Button;
16905         } else if(p = strstr(opt->name, " -save")) {
16906             opt->type = SaveButton;
16907         } else return FALSE;
16908         *p = 0; // terminate option name
16909         // now look if the command-line options define a setting for this engine option.
16910         if(cps->optionSettings && cps->optionSettings[0])
16911             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16912         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16913           snprintf(buf, MSG_SIZ, "option %s", p);
16914                 if(p = strstr(buf, ",")) *p = 0;
16915                 if(q = strchr(buf, '=')) switch(opt->type) {
16916                     case ComboBox:
16917                         for(n=0; n<opt->max; n++)
16918                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16919                         break;
16920                     case TextBox:
16921                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16922                         break;
16923                     case Spin:
16924                     case CheckBox:
16925                         opt->value = atoi(q+1);
16926                     default:
16927                         break;
16928                 }
16929                 strcat(buf, "\n");
16930                 SendToProgram(buf, cps);
16931         }
16932         return TRUE;
16933 }
16934
16935 void
16936 FeatureDone (ChessProgramState *cps, int val)
16937 {
16938   DelayedEventCallback cb = GetDelayedEvent();
16939   if ((cb == InitBackEnd3 && cps == &first) ||
16940       (cb == SettingsMenuIfReady && cps == &second) ||
16941       (cb == LoadEngine) ||
16942       (cb == TwoMachinesEventIfReady)) {
16943     CancelDelayedEvent();
16944     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16945   }
16946   cps->initDone = val;
16947   if(val) cps->reload = FALSE;
16948 }
16949
16950 /* Parse feature command from engine */
16951 void
16952 ParseFeatures (char *args, ChessProgramState *cps)
16953 {
16954   char *p = args;
16955   char *q = NULL;
16956   int val;
16957   char buf[MSG_SIZ];
16958
16959   for (;;) {
16960     while (*p == ' ') p++;
16961     if (*p == NULLCHAR) return;
16962
16963     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16964     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16965     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16966     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16967     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16968     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16969     if (BoolFeature(&p, "reuse", &val, cps)) {
16970       /* Engine can disable reuse, but can't enable it if user said no */
16971       if (!val) cps->reuse = FALSE;
16972       continue;
16973     }
16974     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16975     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16976       if (gameMode == TwoMachinesPlay) {
16977         DisplayTwoMachinesTitle();
16978       } else {
16979         DisplayTitle("");
16980       }
16981       continue;
16982     }
16983     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16984     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16985     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16986     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16987     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16988     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16989     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16990     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16991     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16992     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16993     if (IntFeature(&p, "done", &val, cps)) {
16994       FeatureDone(cps, val);
16995       continue;
16996     }
16997     /* Added by Tord: */
16998     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16999     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17000     /* End of additions by Tord */
17001
17002     /* [HGM] added features: */
17003     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17004     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17005     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17006     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17007     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17008     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17009     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17010     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17011         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17012         FREE(cps->option[cps->nrOptions].name);
17013         cps->option[cps->nrOptions].name = q; q = NULL;
17014         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17015           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17016             SendToProgram(buf, cps);
17017             continue;
17018         }
17019         if(cps->nrOptions >= MAX_OPTIONS) {
17020             cps->nrOptions--;
17021             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17022             DisplayError(buf, 0);
17023         }
17024         continue;
17025     }
17026     /* End of additions by HGM */
17027
17028     /* unknown feature: complain and skip */
17029     q = p;
17030     while (*q && *q != '=') q++;
17031     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17032     SendToProgram(buf, cps);
17033     p = q;
17034     if (*p == '=') {
17035       p++;
17036       if (*p == '\"') {
17037         p++;
17038         while (*p && *p != '\"') p++;
17039         if (*p == '\"') p++;
17040       } else {
17041         while (*p && *p != ' ') p++;
17042       }
17043     }
17044   }
17045
17046 }
17047
17048 void
17049 PeriodicUpdatesEvent (int newState)
17050 {
17051     if (newState == appData.periodicUpdates)
17052       return;
17053
17054     appData.periodicUpdates=newState;
17055
17056     /* Display type changes, so update it now */
17057 //    DisplayAnalysis();
17058
17059     /* Get the ball rolling again... */
17060     if (newState) {
17061         AnalysisPeriodicEvent(1);
17062         StartAnalysisClock();
17063     }
17064 }
17065
17066 void
17067 PonderNextMoveEvent (int newState)
17068 {
17069     if (newState == appData.ponderNextMove) return;
17070     if (gameMode == EditPosition) EditPositionDone(TRUE);
17071     if (newState) {
17072         SendToProgram("hard\n", &first);
17073         if (gameMode == TwoMachinesPlay) {
17074             SendToProgram("hard\n", &second);
17075         }
17076     } else {
17077         SendToProgram("easy\n", &first);
17078         thinkOutput[0] = NULLCHAR;
17079         if (gameMode == TwoMachinesPlay) {
17080             SendToProgram("easy\n", &second);
17081         }
17082     }
17083     appData.ponderNextMove = newState;
17084 }
17085
17086 void
17087 NewSettingEvent (int option, int *feature, char *command, int value)
17088 {
17089     char buf[MSG_SIZ];
17090
17091     if (gameMode == EditPosition) EditPositionDone(TRUE);
17092     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17093     if(feature == NULL || *feature) SendToProgram(buf, &first);
17094     if (gameMode == TwoMachinesPlay) {
17095         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17096     }
17097 }
17098
17099 void
17100 ShowThinkingEvent ()
17101 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17102 {
17103     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17104     int newState = appData.showThinking
17105         // [HGM] thinking: other features now need thinking output as well
17106         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17107
17108     if (oldState == newState) return;
17109     oldState = newState;
17110     if (gameMode == EditPosition) EditPositionDone(TRUE);
17111     if (oldState) {
17112         SendToProgram("post\n", &first);
17113         if (gameMode == TwoMachinesPlay) {
17114             SendToProgram("post\n", &second);
17115         }
17116     } else {
17117         SendToProgram("nopost\n", &first);
17118         thinkOutput[0] = NULLCHAR;
17119         if (gameMode == TwoMachinesPlay) {
17120             SendToProgram("nopost\n", &second);
17121         }
17122     }
17123 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17124 }
17125
17126 void
17127 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17128 {
17129   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17130   if (pr == NoProc) return;
17131   AskQuestion(title, question, replyPrefix, pr);
17132 }
17133
17134 void
17135 TypeInEvent (char firstChar)
17136 {
17137     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17138         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17139         gameMode == AnalyzeMode || gameMode == EditGame ||
17140         gameMode == EditPosition || gameMode == IcsExamining ||
17141         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17142         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17143                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17144                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17145         gameMode == Training) PopUpMoveDialog(firstChar);
17146 }
17147
17148 void
17149 TypeInDoneEvent (char *move)
17150 {
17151         Board board;
17152         int n, fromX, fromY, toX, toY;
17153         char promoChar;
17154         ChessMove moveType;
17155
17156         // [HGM] FENedit
17157         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17158                 EditPositionPasteFEN(move);
17159                 return;
17160         }
17161         // [HGM] movenum: allow move number to be typed in any mode
17162         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17163           ToNrEvent(2*n-1);
17164           return;
17165         }
17166         // undocumented kludge: allow command-line option to be typed in!
17167         // (potentially fatal, and does not implement the effect of the option.)
17168         // should only be used for options that are values on which future decisions will be made,
17169         // and definitely not on options that would be used during initialization.
17170         if(strstr(move, "!!! -") == move) {
17171             ParseArgsFromString(move+4);
17172             return;
17173         }
17174
17175       if (gameMode != EditGame && currentMove != forwardMostMove &&
17176         gameMode != Training) {
17177         DisplayMoveError(_("Displayed move is not current"));
17178       } else {
17179         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17180           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17181         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17182         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17183           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17184           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17185         } else {
17186           DisplayMoveError(_("Could not parse move"));
17187         }
17188       }
17189 }
17190
17191 void
17192 DisplayMove (int moveNumber)
17193 {
17194     char message[MSG_SIZ];
17195     char res[MSG_SIZ];
17196     char cpThinkOutput[MSG_SIZ];
17197
17198     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17199
17200     if (moveNumber == forwardMostMove - 1 ||
17201         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17202
17203         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17204
17205         if (strchr(cpThinkOutput, '\n')) {
17206             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17207         }
17208     } else {
17209         *cpThinkOutput = NULLCHAR;
17210     }
17211
17212     /* [AS] Hide thinking from human user */
17213     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17214         *cpThinkOutput = NULLCHAR;
17215         if( thinkOutput[0] != NULLCHAR ) {
17216             int i;
17217
17218             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17219                 cpThinkOutput[i] = '.';
17220             }
17221             cpThinkOutput[i] = NULLCHAR;
17222             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17223         }
17224     }
17225
17226     if (moveNumber == forwardMostMove - 1 &&
17227         gameInfo.resultDetails != NULL) {
17228         if (gameInfo.resultDetails[0] == NULLCHAR) {
17229           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17230         } else {
17231           snprintf(res, MSG_SIZ, " {%s} %s",
17232                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17233         }
17234     } else {
17235         res[0] = NULLCHAR;
17236     }
17237
17238     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17239         DisplayMessage(res, cpThinkOutput);
17240     } else {
17241       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17242                 WhiteOnMove(moveNumber) ? " " : ".. ",
17243                 parseList[moveNumber], res);
17244         DisplayMessage(message, cpThinkOutput);
17245     }
17246 }
17247
17248 void
17249 DisplayComment (int moveNumber, char *text)
17250 {
17251     char title[MSG_SIZ];
17252
17253     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17254       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17255     } else {
17256       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17257               WhiteOnMove(moveNumber) ? " " : ".. ",
17258               parseList[moveNumber]);
17259     }
17260     if (text != NULL && (appData.autoDisplayComment || commentUp))
17261         CommentPopUp(title, text);
17262 }
17263
17264 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17265  * might be busy thinking or pondering.  It can be omitted if your
17266  * gnuchess is configured to stop thinking immediately on any user
17267  * input.  However, that gnuchess feature depends on the FIONREAD
17268  * ioctl, which does not work properly on some flavors of Unix.
17269  */
17270 void
17271 Attention (ChessProgramState *cps)
17272 {
17273 #if ATTENTION
17274     if (!cps->useSigint) return;
17275     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17276     switch (gameMode) {
17277       case MachinePlaysWhite:
17278       case MachinePlaysBlack:
17279       case TwoMachinesPlay:
17280       case IcsPlayingWhite:
17281       case IcsPlayingBlack:
17282       case AnalyzeMode:
17283       case AnalyzeFile:
17284         /* Skip if we know it isn't thinking */
17285         if (!cps->maybeThinking) return;
17286         if (appData.debugMode)
17287           fprintf(debugFP, "Interrupting %s\n", cps->which);
17288         InterruptChildProcess(cps->pr);
17289         cps->maybeThinking = FALSE;
17290         break;
17291       default:
17292         break;
17293     }
17294 #endif /*ATTENTION*/
17295 }
17296
17297 int
17298 CheckFlags ()
17299 {
17300     if (whiteTimeRemaining <= 0) {
17301         if (!whiteFlag) {
17302             whiteFlag = TRUE;
17303             if (appData.icsActive) {
17304                 if (appData.autoCallFlag &&
17305                     gameMode == IcsPlayingBlack && !blackFlag) {
17306                   SendToICS(ics_prefix);
17307                   SendToICS("flag\n");
17308                 }
17309             } else {
17310                 if (blackFlag) {
17311                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17312                 } else {
17313                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17314                     if (appData.autoCallFlag) {
17315                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17316                         return TRUE;
17317                     }
17318                 }
17319             }
17320         }
17321     }
17322     if (blackTimeRemaining <= 0) {
17323         if (!blackFlag) {
17324             blackFlag = TRUE;
17325             if (appData.icsActive) {
17326                 if (appData.autoCallFlag &&
17327                     gameMode == IcsPlayingWhite && !whiteFlag) {
17328                   SendToICS(ics_prefix);
17329                   SendToICS("flag\n");
17330                 }
17331             } else {
17332                 if (whiteFlag) {
17333                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17334                 } else {
17335                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17336                     if (appData.autoCallFlag) {
17337                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17338                         return TRUE;
17339                     }
17340                 }
17341             }
17342         }
17343     }
17344     return FALSE;
17345 }
17346
17347 void
17348 CheckTimeControl ()
17349 {
17350     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17351         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17352
17353     /*
17354      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17355      */
17356     if ( !WhiteOnMove(forwardMostMove) ) {
17357         /* White made time control */
17358         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17359         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17360         /* [HGM] time odds: correct new time quota for time odds! */
17361                                             / WhitePlayer()->timeOdds;
17362         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17363     } else {
17364         lastBlack -= blackTimeRemaining;
17365         /* Black made time control */
17366         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17367                                             / WhitePlayer()->other->timeOdds;
17368         lastWhite = whiteTimeRemaining;
17369     }
17370 }
17371
17372 void
17373 DisplayBothClocks ()
17374 {
17375     int wom = gameMode == EditPosition ?
17376       !blackPlaysFirst : WhiteOnMove(currentMove);
17377     DisplayWhiteClock(whiteTimeRemaining, wom);
17378     DisplayBlackClock(blackTimeRemaining, !wom);
17379 }
17380
17381
17382 /* Timekeeping seems to be a portability nightmare.  I think everyone
17383    has ftime(), but I'm really not sure, so I'm including some ifdefs
17384    to use other calls if you don't.  Clocks will be less accurate if
17385    you have neither ftime nor gettimeofday.
17386 */
17387
17388 /* VS 2008 requires the #include outside of the function */
17389 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17390 #include <sys/timeb.h>
17391 #endif
17392
17393 /* Get the current time as a TimeMark */
17394 void
17395 GetTimeMark (TimeMark *tm)
17396 {
17397 #if HAVE_GETTIMEOFDAY
17398
17399     struct timeval timeVal;
17400     struct timezone timeZone;
17401
17402     gettimeofday(&timeVal, &timeZone);
17403     tm->sec = (long) timeVal.tv_sec;
17404     tm->ms = (int) (timeVal.tv_usec / 1000L);
17405
17406 #else /*!HAVE_GETTIMEOFDAY*/
17407 #if HAVE_FTIME
17408
17409 // include <sys/timeb.h> / moved to just above start of function
17410     struct timeb timeB;
17411
17412     ftime(&timeB);
17413     tm->sec = (long) timeB.time;
17414     tm->ms = (int) timeB.millitm;
17415
17416 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17417     tm->sec = (long) time(NULL);
17418     tm->ms = 0;
17419 #endif
17420 #endif
17421 }
17422
17423 /* Return the difference in milliseconds between two
17424    time marks.  We assume the difference will fit in a long!
17425 */
17426 long
17427 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17428 {
17429     return 1000L*(tm2->sec - tm1->sec) +
17430            (long) (tm2->ms - tm1->ms);
17431 }
17432
17433
17434 /*
17435  * Code to manage the game clocks.
17436  *
17437  * In tournament play, black starts the clock and then white makes a move.
17438  * We give the human user a slight advantage if he is playing white---the
17439  * clocks don't run until he makes his first move, so it takes zero time.
17440  * Also, we don't account for network lag, so we could get out of sync
17441  * with GNU Chess's clock -- but then, referees are always right.
17442  */
17443
17444 static TimeMark tickStartTM;
17445 static long intendedTickLength;
17446
17447 long
17448 NextTickLength (long timeRemaining)
17449 {
17450     long nominalTickLength, nextTickLength;
17451
17452     if (timeRemaining > 0L && timeRemaining <= 10000L)
17453       nominalTickLength = 100L;
17454     else
17455       nominalTickLength = 1000L;
17456     nextTickLength = timeRemaining % nominalTickLength;
17457     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17458
17459     return nextTickLength;
17460 }
17461
17462 /* Adjust clock one minute up or down */
17463 void
17464 AdjustClock (Boolean which, int dir)
17465 {
17466     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17467     if(which) blackTimeRemaining += 60000*dir;
17468     else      whiteTimeRemaining += 60000*dir;
17469     DisplayBothClocks();
17470     adjustedClock = TRUE;
17471 }
17472
17473 /* Stop clocks and reset to a fresh time control */
17474 void
17475 ResetClocks ()
17476 {
17477     (void) StopClockTimer();
17478     if (appData.icsActive) {
17479         whiteTimeRemaining = blackTimeRemaining = 0;
17480     } else if (searchTime) {
17481         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17482         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17483     } else { /* [HGM] correct new time quote for time odds */
17484         whiteTC = blackTC = fullTimeControlString;
17485         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17486         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17487     }
17488     if (whiteFlag || blackFlag) {
17489         DisplayTitle("");
17490         whiteFlag = blackFlag = FALSE;
17491     }
17492     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17493     DisplayBothClocks();
17494     adjustedClock = FALSE;
17495 }
17496
17497 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17498
17499 /* Decrement running clock by amount of time that has passed */
17500 void
17501 DecrementClocks ()
17502 {
17503     long timeRemaining;
17504     long lastTickLength, fudge;
17505     TimeMark now;
17506
17507     if (!appData.clockMode) return;
17508     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17509
17510     GetTimeMark(&now);
17511
17512     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17513
17514     /* Fudge if we woke up a little too soon */
17515     fudge = intendedTickLength - lastTickLength;
17516     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17517
17518     if (WhiteOnMove(forwardMostMove)) {
17519         if(whiteNPS >= 0) lastTickLength = 0;
17520         timeRemaining = whiteTimeRemaining -= lastTickLength;
17521         if(timeRemaining < 0 && !appData.icsActive) {
17522             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17523             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17524                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17525                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17526             }
17527         }
17528         DisplayWhiteClock(whiteTimeRemaining - fudge,
17529                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17530     } else {
17531         if(blackNPS >= 0) lastTickLength = 0;
17532         timeRemaining = blackTimeRemaining -= lastTickLength;
17533         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17534             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17535             if(suddenDeath) {
17536                 blackStartMove = forwardMostMove;
17537                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17538             }
17539         }
17540         DisplayBlackClock(blackTimeRemaining - fudge,
17541                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17542     }
17543     if (CheckFlags()) return;
17544
17545     if(twoBoards) { // count down secondary board's clocks as well
17546         activePartnerTime -= lastTickLength;
17547         partnerUp = 1;
17548         if(activePartner == 'W')
17549             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17550         else
17551             DisplayBlackClock(activePartnerTime, TRUE);
17552         partnerUp = 0;
17553     }
17554
17555     tickStartTM = now;
17556     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17557     StartClockTimer(intendedTickLength);
17558
17559     /* if the time remaining has fallen below the alarm threshold, sound the
17560      * alarm. if the alarm has sounded and (due to a takeback or time control
17561      * with increment) the time remaining has increased to a level above the
17562      * threshold, reset the alarm so it can sound again.
17563      */
17564
17565     if (appData.icsActive && appData.icsAlarm) {
17566
17567         /* make sure we are dealing with the user's clock */
17568         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17569                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17570            )) return;
17571
17572         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17573             alarmSounded = FALSE;
17574         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17575             PlayAlarmSound();
17576             alarmSounded = TRUE;
17577         }
17578     }
17579 }
17580
17581
17582 /* A player has just moved, so stop the previously running
17583    clock and (if in clock mode) start the other one.
17584    We redisplay both clocks in case we're in ICS mode, because
17585    ICS gives us an update to both clocks after every move.
17586    Note that this routine is called *after* forwardMostMove
17587    is updated, so the last fractional tick must be subtracted
17588    from the color that is *not* on move now.
17589 */
17590 void
17591 SwitchClocks (int newMoveNr)
17592 {
17593     long lastTickLength;
17594     TimeMark now;
17595     int flagged = FALSE;
17596
17597     GetTimeMark(&now);
17598
17599     if (StopClockTimer() && appData.clockMode) {
17600         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17601         if (!WhiteOnMove(forwardMostMove)) {
17602             if(blackNPS >= 0) lastTickLength = 0;
17603             blackTimeRemaining -= lastTickLength;
17604            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17605 //         if(pvInfoList[forwardMostMove].time == -1)
17606                  pvInfoList[forwardMostMove].time =               // use GUI time
17607                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17608         } else {
17609            if(whiteNPS >= 0) lastTickLength = 0;
17610            whiteTimeRemaining -= lastTickLength;
17611            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17612 //         if(pvInfoList[forwardMostMove].time == -1)
17613                  pvInfoList[forwardMostMove].time =
17614                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17615         }
17616         flagged = CheckFlags();
17617     }
17618     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17619     CheckTimeControl();
17620
17621     if (flagged || !appData.clockMode) return;
17622
17623     switch (gameMode) {
17624       case MachinePlaysBlack:
17625       case MachinePlaysWhite:
17626       case BeginningOfGame:
17627         if (pausing) return;
17628         break;
17629
17630       case EditGame:
17631       case PlayFromGameFile:
17632       case IcsExamining:
17633         return;
17634
17635       default:
17636         break;
17637     }
17638
17639     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17640         if(WhiteOnMove(forwardMostMove))
17641              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17642         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17643     }
17644
17645     tickStartTM = now;
17646     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17647       whiteTimeRemaining : blackTimeRemaining);
17648     StartClockTimer(intendedTickLength);
17649 }
17650
17651
17652 /* Stop both clocks */
17653 void
17654 StopClocks ()
17655 {
17656     long lastTickLength;
17657     TimeMark now;
17658
17659     if (!StopClockTimer()) return;
17660     if (!appData.clockMode) return;
17661
17662     GetTimeMark(&now);
17663
17664     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17665     if (WhiteOnMove(forwardMostMove)) {
17666         if(whiteNPS >= 0) lastTickLength = 0;
17667         whiteTimeRemaining -= lastTickLength;
17668         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17669     } else {
17670         if(blackNPS >= 0) lastTickLength = 0;
17671         blackTimeRemaining -= lastTickLength;
17672         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17673     }
17674     CheckFlags();
17675 }
17676
17677 /* Start clock of player on move.  Time may have been reset, so
17678    if clock is already running, stop and restart it. */
17679 void
17680 StartClocks ()
17681 {
17682     (void) StopClockTimer(); /* in case it was running already */
17683     DisplayBothClocks();
17684     if (CheckFlags()) return;
17685
17686     if (!appData.clockMode) return;
17687     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17688
17689     GetTimeMark(&tickStartTM);
17690     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17691       whiteTimeRemaining : blackTimeRemaining);
17692
17693    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17694     whiteNPS = blackNPS = -1;
17695     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17696        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17697         whiteNPS = first.nps;
17698     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17699        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17700         blackNPS = first.nps;
17701     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17702         whiteNPS = second.nps;
17703     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17704         blackNPS = second.nps;
17705     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17706
17707     StartClockTimer(intendedTickLength);
17708 }
17709
17710 char *
17711 TimeString (long ms)
17712 {
17713     long second, minute, hour, day;
17714     char *sign = "";
17715     static char buf[32];
17716
17717     if (ms > 0 && ms <= 9900) {
17718       /* convert milliseconds to tenths, rounding up */
17719       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17720
17721       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17722       return buf;
17723     }
17724
17725     /* convert milliseconds to seconds, rounding up */
17726     /* use floating point to avoid strangeness of integer division
17727        with negative dividends on many machines */
17728     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17729
17730     if (second < 0) {
17731         sign = "-";
17732         second = -second;
17733     }
17734
17735     day = second / (60 * 60 * 24);
17736     second = second % (60 * 60 * 24);
17737     hour = second / (60 * 60);
17738     second = second % (60 * 60);
17739     minute = second / 60;
17740     second = second % 60;
17741
17742     if (day > 0)
17743       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17744               sign, day, hour, minute, second);
17745     else if (hour > 0)
17746       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17747     else
17748       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17749
17750     return buf;
17751 }
17752
17753
17754 /*
17755  * This is necessary because some C libraries aren't ANSI C compliant yet.
17756  */
17757 char *
17758 StrStr (char *string, char *match)
17759 {
17760     int i, length;
17761
17762     length = strlen(match);
17763
17764     for (i = strlen(string) - length; i >= 0; i--, string++)
17765       if (!strncmp(match, string, length))
17766         return string;
17767
17768     return NULL;
17769 }
17770
17771 char *
17772 StrCaseStr (char *string, char *match)
17773 {
17774     int i, j, length;
17775
17776     length = strlen(match);
17777
17778     for (i = strlen(string) - length; i >= 0; i--, string++) {
17779         for (j = 0; j < length; j++) {
17780             if (ToLower(match[j]) != ToLower(string[j]))
17781               break;
17782         }
17783         if (j == length) return string;
17784     }
17785
17786     return NULL;
17787 }
17788
17789 #ifndef _amigados
17790 int
17791 StrCaseCmp (char *s1, char *s2)
17792 {
17793     char c1, c2;
17794
17795     for (;;) {
17796         c1 = ToLower(*s1++);
17797         c2 = ToLower(*s2++);
17798         if (c1 > c2) return 1;
17799         if (c1 < c2) return -1;
17800         if (c1 == NULLCHAR) return 0;
17801     }
17802 }
17803
17804
17805 int
17806 ToLower (int c)
17807 {
17808     return isupper(c) ? tolower(c) : c;
17809 }
17810
17811
17812 int
17813 ToUpper (int c)
17814 {
17815     return islower(c) ? toupper(c) : c;
17816 }
17817 #endif /* !_amigados    */
17818
17819 char *
17820 StrSave (char *s)
17821 {
17822   char *ret;
17823
17824   if ((ret = (char *) malloc(strlen(s) + 1)))
17825     {
17826       safeStrCpy(ret, s, strlen(s)+1);
17827     }
17828   return ret;
17829 }
17830
17831 char *
17832 StrSavePtr (char *s, char **savePtr)
17833 {
17834     if (*savePtr) {
17835         free(*savePtr);
17836     }
17837     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17838       safeStrCpy(*savePtr, s, strlen(s)+1);
17839     }
17840     return(*savePtr);
17841 }
17842
17843 char *
17844 PGNDate ()
17845 {
17846     time_t clock;
17847     struct tm *tm;
17848     char buf[MSG_SIZ];
17849
17850     clock = time((time_t *)NULL);
17851     tm = localtime(&clock);
17852     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17853             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17854     return StrSave(buf);
17855 }
17856
17857
17858 char *
17859 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17860 {
17861     int i, j, fromX, fromY, toX, toY;
17862     int whiteToPlay;
17863     char buf[MSG_SIZ];
17864     char *p, *q;
17865     int emptycount;
17866     ChessSquare piece;
17867
17868     whiteToPlay = (gameMode == EditPosition) ?
17869       !blackPlaysFirst : (move % 2 == 0);
17870     p = buf;
17871
17872     /* Piece placement data */
17873     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17874         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17875         emptycount = 0;
17876         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17877             if (boards[move][i][j] == EmptySquare) {
17878                 emptycount++;
17879             } else { ChessSquare piece = boards[move][i][j];
17880                 if (emptycount > 0) {
17881                     if(emptycount<10) /* [HGM] can be >= 10 */
17882                         *p++ = '0' + emptycount;
17883                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17884                     emptycount = 0;
17885                 }
17886                 if(PieceToChar(piece) == '+') {
17887                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17888                     *p++ = '+';
17889                     piece = (ChessSquare)(CHUDEMOTED piece);
17890                 }
17891                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17892                 if(*p = PieceSuffix(piece)) p++;
17893                 if(p[-1] == '~') {
17894                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17895                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17896                     *p++ = '~';
17897                 }
17898             }
17899         }
17900         if (emptycount > 0) {
17901             if(emptycount<10) /* [HGM] can be >= 10 */
17902                 *p++ = '0' + emptycount;
17903             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17904             emptycount = 0;
17905         }
17906         *p++ = '/';
17907     }
17908     *(p - 1) = ' ';
17909
17910     /* [HGM] print Crazyhouse or Shogi holdings */
17911     if( gameInfo.holdingsWidth ) {
17912         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17913         q = p;
17914         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17915             piece = boards[move][i][BOARD_WIDTH-1];
17916             if( piece != EmptySquare )
17917               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17918                   *p++ = PieceToChar(piece);
17919         }
17920         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17921             piece = boards[move][BOARD_HEIGHT-i-1][0];
17922             if( piece != EmptySquare )
17923               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17924                   *p++ = PieceToChar(piece);
17925         }
17926
17927         if( q == p ) *p++ = '-';
17928         *p++ = ']';
17929         *p++ = ' ';
17930     }
17931
17932     /* Active color */
17933     *p++ = whiteToPlay ? 'w' : 'b';
17934     *p++ = ' ';
17935
17936   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17937     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17938   } else {
17939   if(nrCastlingRights) {
17940      int handW=0, handB=0;
17941      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17942         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17943         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17944      }
17945      q = p;
17946      if(appData.fischerCastling) {
17947         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17948            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17949                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17950         } else {
17951        /* [HGM] write directly from rights */
17952            if(boards[move][CASTLING][2] != NoRights &&
17953               boards[move][CASTLING][0] != NoRights   )
17954                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17955            if(boards[move][CASTLING][2] != NoRights &&
17956               boards[move][CASTLING][1] != NoRights   )
17957                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17958         }
17959         if(handB) {
17960            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17961                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17962         } else {
17963            if(boards[move][CASTLING][5] != NoRights &&
17964               boards[move][CASTLING][3] != NoRights   )
17965                 *p++ = boards[move][CASTLING][3] + AAA;
17966            if(boards[move][CASTLING][5] != NoRights &&
17967               boards[move][CASTLING][4] != NoRights   )
17968                 *p++ = boards[move][CASTLING][4] + AAA;
17969         }
17970      } else {
17971
17972         /* [HGM] write true castling rights */
17973         if( nrCastlingRights == 6 ) {
17974             int q, k=0;
17975             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17976                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17977             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17978                  boards[move][CASTLING][2] != NoRights  );
17979             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17980                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17981                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17982                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17983             }
17984             if(q) *p++ = 'Q';
17985             k = 0;
17986             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17987                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17988             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17989                  boards[move][CASTLING][5] != NoRights  );
17990             if(handB) {
17991                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17992                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17993                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17994             }
17995             if(q) *p++ = 'q';
17996         }
17997      }
17998      if (q == p) *p++ = '-'; /* No castling rights */
17999      *p++ = ' ';
18000   }
18001
18002   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18003      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18004      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18005     /* En passant target square */
18006     if (move > backwardMostMove) {
18007         fromX = moveList[move - 1][0] - AAA;
18008         fromY = moveList[move - 1][1] - ONE;
18009         toX = moveList[move - 1][2] - AAA;
18010         toY = moveList[move - 1][3] - ONE;
18011         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18012             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18013             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18014             fromX == toX) {
18015             /* 2-square pawn move just happened */
18016             *p++ = toX + AAA;
18017             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18018         } else {
18019             *p++ = '-';
18020         }
18021     } else if(move == backwardMostMove) {
18022         // [HGM] perhaps we should always do it like this, and forget the above?
18023         if((signed char)boards[move][EP_STATUS] >= 0) {
18024             *p++ = boards[move][EP_STATUS] + AAA;
18025             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18026         } else {
18027             *p++ = '-';
18028         }
18029     } else {
18030         *p++ = '-';
18031     }
18032     *p++ = ' ';
18033   }
18034   }
18035
18036     if(moveCounts)
18037     {   int i = 0, j=move;
18038
18039         /* [HGM] find reversible plies */
18040         if (appData.debugMode) { int k;
18041             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18042             for(k=backwardMostMove; k<=forwardMostMove; k++)
18043                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18044
18045         }
18046
18047         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18048         if( j == backwardMostMove ) i += initialRulePlies;
18049         sprintf(p, "%d ", i);
18050         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18051
18052         /* Fullmove number */
18053         sprintf(p, "%d", (move / 2) + 1);
18054     } else *--p = NULLCHAR;
18055
18056     return StrSave(buf);
18057 }
18058
18059 Boolean
18060 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18061 {
18062     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18063     char *p, c;
18064     int emptycount, virgin[BOARD_FILES];
18065     ChessSquare piece;
18066
18067     p = fen;
18068
18069     /* Piece placement data */
18070     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18071         j = 0;
18072         for (;;) {
18073             if (*p == '/' || *p == ' ' || *p == '[' ) {
18074                 if(j > w) w = j;
18075                 emptycount = gameInfo.boardWidth - j;
18076                 while (emptycount--)
18077                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18078                 if (*p == '/') p++;
18079                 else if(autoSize) { // we stumbled unexpectedly into end of board
18080                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18081                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18082                     }
18083                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18084                 }
18085                 break;
18086 #if(BOARD_FILES >= 10)*0
18087             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18088                 p++; emptycount=10;
18089                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18090                 while (emptycount--)
18091                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18092 #endif
18093             } else if (*p == '*') {
18094                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18095             } else if (isdigit(*p)) {
18096                 emptycount = *p++ - '0';
18097                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18098                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18099                 while (emptycount--)
18100                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18101             } else if (*p == '<') {
18102                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18103                 else if (i != 0 || !shuffle) return FALSE;
18104                 p++;
18105             } else if (shuffle && *p == '>') {
18106                 p++; // for now ignore closing shuffle range, and assume rank-end
18107             } else if (*p == '?') {
18108                 if (j >= gameInfo.boardWidth) return FALSE;
18109                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18110                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18111             } else if (*p == '+' || isalpha(*p)) {
18112                 char *q, *s = SUFFIXES;
18113                 if (j >= gameInfo.boardWidth) return FALSE;
18114                 if(*p=='+') {
18115                     char c = *++p;
18116                     if(q = strchr(s, p[1])) p++;
18117                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18118                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18119                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18120                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18121                 } else {
18122                     char c = *p++;
18123                     if(q = strchr(s, *p)) p++;
18124                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18125                 }
18126
18127                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18128                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18129                     piece = (ChessSquare) (PROMOTED piece);
18130                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18131                     p++;
18132                 }
18133                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18134                 if(piece == WhiteKing) wKingRank = i;
18135                 if(piece == BlackKing) bKingRank = i;
18136             } else {
18137                 return FALSE;
18138             }
18139         }
18140     }
18141     while (*p == '/' || *p == ' ') p++;
18142
18143     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18144
18145     /* [HGM] by default clear Crazyhouse holdings, if present */
18146     if(gameInfo.holdingsWidth) {
18147        for(i=0; i<BOARD_HEIGHT; i++) {
18148            board[i][0]             = EmptySquare; /* black holdings */
18149            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18150            board[i][1]             = (ChessSquare) 0; /* black counts */
18151            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18152        }
18153     }
18154
18155     /* [HGM] look for Crazyhouse holdings here */
18156     while(*p==' ') p++;
18157     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18158         int swap=0, wcnt=0, bcnt=0;
18159         if(*p == '[') p++;
18160         if(*p == '<') swap++, p++;
18161         if(*p == '-' ) p++; /* empty holdings */ else {
18162             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18163             /* if we would allow FEN reading to set board size, we would   */
18164             /* have to add holdings and shift the board read so far here   */
18165             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18166                 p++;
18167                 if((int) piece >= (int) BlackPawn ) {
18168                     i = (int)piece - (int)BlackPawn;
18169                     i = PieceToNumber((ChessSquare)i);
18170                     if( i >= gameInfo.holdingsSize ) return FALSE;
18171                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18172                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18173                     bcnt++;
18174                 } else {
18175                     i = (int)piece - (int)WhitePawn;
18176                     i = PieceToNumber((ChessSquare)i);
18177                     if( i >= gameInfo.holdingsSize ) return FALSE;
18178                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18179                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18180                     wcnt++;
18181                 }
18182             }
18183             if(subst) { // substitute back-rank question marks by holdings pieces
18184                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18185                     int k, m, n = bcnt + 1;
18186                     if(board[0][j] == ClearBoard) {
18187                         if(!wcnt) return FALSE;
18188                         n = rand() % wcnt;
18189                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18190                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18191                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18192                             break;
18193                         }
18194                     }
18195                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18196                         if(!bcnt) return FALSE;
18197                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18198                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18199                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18200                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18201                             break;
18202                         }
18203                     }
18204                 }
18205                 subst = 0;
18206             }
18207         }
18208         if(*p == ']') p++;
18209     }
18210
18211     if(subst) return FALSE; // substitution requested, but no holdings
18212
18213     while(*p == ' ') p++;
18214
18215     /* Active color */
18216     c = *p++;
18217     if(appData.colorNickNames) {
18218       if( c == appData.colorNickNames[0] ) c = 'w'; else
18219       if( c == appData.colorNickNames[1] ) c = 'b';
18220     }
18221     switch (c) {
18222       case 'w':
18223         *blackPlaysFirst = FALSE;
18224         break;
18225       case 'b':
18226         *blackPlaysFirst = TRUE;
18227         break;
18228       default:
18229         return FALSE;
18230     }
18231
18232     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18233     /* return the extra info in global variiables             */
18234
18235     while(*p==' ') p++;
18236
18237     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18238         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18239         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18240     }
18241
18242     /* set defaults in case FEN is incomplete */
18243     board[EP_STATUS] = EP_UNKNOWN;
18244     for(i=0; i<nrCastlingRights; i++ ) {
18245         board[CASTLING][i] =
18246             appData.fischerCastling ? NoRights : initialRights[i];
18247     }   /* assume possible unless obviously impossible */
18248     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18249     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18250     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18251                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18252     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18253     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18254     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18255                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18256     FENrulePlies = 0;
18257
18258     if(nrCastlingRights) {
18259       int fischer = 0;
18260       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18261       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18262           /* castling indicator present, so default becomes no castlings */
18263           for(i=0; i<nrCastlingRights; i++ ) {
18264                  board[CASTLING][i] = NoRights;
18265           }
18266       }
18267       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18268              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18269              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18270              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18271         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18272
18273         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18274             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18275             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18276         }
18277         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18278             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18279         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18280                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18281         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18282                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18283         switch(c) {
18284           case'K':
18285               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18286               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18287               board[CASTLING][2] = whiteKingFile;
18288               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18289               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18290               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18291               break;
18292           case'Q':
18293               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18294               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18295               board[CASTLING][2] = whiteKingFile;
18296               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18297               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18298               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18299               break;
18300           case'k':
18301               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18302               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18303               board[CASTLING][5] = blackKingFile;
18304               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18305               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18306               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18307               break;
18308           case'q':
18309               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18310               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18311               board[CASTLING][5] = blackKingFile;
18312               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18313               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18314               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18315           case '-':
18316               break;
18317           default: /* FRC castlings */
18318               if(c >= 'a') { /* black rights */
18319                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18320                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18321                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18322                   if(i == BOARD_RGHT) break;
18323                   board[CASTLING][5] = i;
18324                   c -= AAA;
18325                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18326                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18327                   if(c > i)
18328                       board[CASTLING][3] = c;
18329                   else
18330                       board[CASTLING][4] = c;
18331               } else { /* white rights */
18332                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18333                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18334                     if(board[0][i] == WhiteKing) break;
18335                   if(i == BOARD_RGHT) break;
18336                   board[CASTLING][2] = i;
18337                   c -= AAA - 'a' + 'A';
18338                   if(board[0][c] >= WhiteKing) break;
18339                   if(c > i)
18340                       board[CASTLING][0] = c;
18341                   else
18342                       board[CASTLING][1] = c;
18343               }
18344         }
18345       }
18346       for(i=0; i<nrCastlingRights; i++)
18347         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18348       if(gameInfo.variant == VariantSChess)
18349         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18350       if(fischer && shuffle) appData.fischerCastling = TRUE;
18351     if (appData.debugMode) {
18352         fprintf(debugFP, "FEN castling rights:");
18353         for(i=0; i<nrCastlingRights; i++)
18354         fprintf(debugFP, " %d", board[CASTLING][i]);
18355         fprintf(debugFP, "\n");
18356     }
18357
18358       while(*p==' ') p++;
18359     }
18360
18361     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18362
18363     /* read e.p. field in games that know e.p. capture */
18364     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18365        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18366        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18367       if(*p=='-') {
18368         p++; board[EP_STATUS] = EP_NONE;
18369       } else {
18370          char c = *p++ - AAA;
18371
18372          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18373          if(*p >= '0' && *p <='9') p++;
18374          board[EP_STATUS] = c;
18375       }
18376     }
18377
18378
18379     if(sscanf(p, "%d", &i) == 1) {
18380         FENrulePlies = i; /* 50-move ply counter */
18381         /* (The move number is still ignored)    */
18382     }
18383
18384     return TRUE;
18385 }
18386
18387 void
18388 EditPositionPasteFEN (char *fen)
18389 {
18390   if (fen != NULL) {
18391     Board initial_position;
18392
18393     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18394       DisplayError(_("Bad FEN position in clipboard"), 0);
18395       return ;
18396     } else {
18397       int savedBlackPlaysFirst = blackPlaysFirst;
18398       EditPositionEvent();
18399       blackPlaysFirst = savedBlackPlaysFirst;
18400       CopyBoard(boards[0], initial_position);
18401       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18402       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18403       DisplayBothClocks();
18404       DrawPosition(FALSE, boards[currentMove]);
18405     }
18406   }
18407 }
18408
18409 static char cseq[12] = "\\   ";
18410
18411 Boolean
18412 set_cont_sequence (char *new_seq)
18413 {
18414     int len;
18415     Boolean ret;
18416
18417     // handle bad attempts to set the sequence
18418         if (!new_seq)
18419                 return 0; // acceptable error - no debug
18420
18421     len = strlen(new_seq);
18422     ret = (len > 0) && (len < sizeof(cseq));
18423     if (ret)
18424       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18425     else if (appData.debugMode)
18426       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18427     return ret;
18428 }
18429
18430 /*
18431     reformat a source message so words don't cross the width boundary.  internal
18432     newlines are not removed.  returns the wrapped size (no null character unless
18433     included in source message).  If dest is NULL, only calculate the size required
18434     for the dest buffer.  lp argument indicats line position upon entry, and it's
18435     passed back upon exit.
18436 */
18437 int
18438 wrap (char *dest, char *src, int count, int width, int *lp)
18439 {
18440     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18441
18442     cseq_len = strlen(cseq);
18443     old_line = line = *lp;
18444     ansi = len = clen = 0;
18445
18446     for (i=0; i < count; i++)
18447     {
18448         if (src[i] == '\033')
18449             ansi = 1;
18450
18451         // if we hit the width, back up
18452         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18453         {
18454             // store i & len in case the word is too long
18455             old_i = i, old_len = len;
18456
18457             // find the end of the last word
18458             while (i && src[i] != ' ' && src[i] != '\n')
18459             {
18460                 i--;
18461                 len--;
18462             }
18463
18464             // word too long?  restore i & len before splitting it
18465             if ((old_i-i+clen) >= width)
18466             {
18467                 i = old_i;
18468                 len = old_len;
18469             }
18470
18471             // extra space?
18472             if (i && src[i-1] == ' ')
18473                 len--;
18474
18475             if (src[i] != ' ' && src[i] != '\n')
18476             {
18477                 i--;
18478                 if (len)
18479                     len--;
18480             }
18481
18482             // now append the newline and continuation sequence
18483             if (dest)
18484                 dest[len] = '\n';
18485             len++;
18486             if (dest)
18487                 strncpy(dest+len, cseq, cseq_len);
18488             len += cseq_len;
18489             line = cseq_len;
18490             clen = cseq_len;
18491             continue;
18492         }
18493
18494         if (dest)
18495             dest[len] = src[i];
18496         len++;
18497         if (!ansi)
18498             line++;
18499         if (src[i] == '\n')
18500             line = 0;
18501         if (src[i] == 'm')
18502             ansi = 0;
18503     }
18504     if (dest && appData.debugMode)
18505     {
18506         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18507             count, width, line, len, *lp);
18508         show_bytes(debugFP, src, count);
18509         fprintf(debugFP, "\ndest: ");
18510         show_bytes(debugFP, dest, len);
18511         fprintf(debugFP, "\n");
18512     }
18513     *lp = dest ? line : old_line;
18514
18515     return len;
18516 }
18517
18518 // [HGM] vari: routines for shelving variations
18519 Boolean modeRestore = FALSE;
18520
18521 void
18522 PushInner (int firstMove, int lastMove)
18523 {
18524         int i, j, nrMoves = lastMove - firstMove;
18525
18526         // push current tail of game on stack
18527         savedResult[storedGames] = gameInfo.result;
18528         savedDetails[storedGames] = gameInfo.resultDetails;
18529         gameInfo.resultDetails = NULL;
18530         savedFirst[storedGames] = firstMove;
18531         savedLast [storedGames] = lastMove;
18532         savedFramePtr[storedGames] = framePtr;
18533         framePtr -= nrMoves; // reserve space for the boards
18534         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18535             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18536             for(j=0; j<MOVE_LEN; j++)
18537                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18538             for(j=0; j<2*MOVE_LEN; j++)
18539                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18540             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18541             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18542             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18543             pvInfoList[firstMove+i-1].depth = 0;
18544             commentList[framePtr+i] = commentList[firstMove+i];
18545             commentList[firstMove+i] = NULL;
18546         }
18547
18548         storedGames++;
18549         forwardMostMove = firstMove; // truncate game so we can start variation
18550 }
18551
18552 void
18553 PushTail (int firstMove, int lastMove)
18554 {
18555         if(appData.icsActive) { // only in local mode
18556                 forwardMostMove = currentMove; // mimic old ICS behavior
18557                 return;
18558         }
18559         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18560
18561         PushInner(firstMove, lastMove);
18562         if(storedGames == 1) GreyRevert(FALSE);
18563         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18564 }
18565
18566 void
18567 PopInner (Boolean annotate)
18568 {
18569         int i, j, nrMoves;
18570         char buf[8000], moveBuf[20];
18571
18572         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18573         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18574         nrMoves = savedLast[storedGames] - currentMove;
18575         if(annotate) {
18576                 int cnt = 10;
18577                 if(!WhiteOnMove(currentMove))
18578                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18579                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18580                 for(i=currentMove; i<forwardMostMove; i++) {
18581                         if(WhiteOnMove(i))
18582                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18583                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18584                         strcat(buf, moveBuf);
18585                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18586                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18587                 }
18588                 strcat(buf, ")");
18589         }
18590         for(i=1; i<=nrMoves; i++) { // copy last variation back
18591             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18592             for(j=0; j<MOVE_LEN; j++)
18593                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18594             for(j=0; j<2*MOVE_LEN; j++)
18595                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18596             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18597             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18598             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18599             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18600             commentList[currentMove+i] = commentList[framePtr+i];
18601             commentList[framePtr+i] = NULL;
18602         }
18603         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18604         framePtr = savedFramePtr[storedGames];
18605         gameInfo.result = savedResult[storedGames];
18606         if(gameInfo.resultDetails != NULL) {
18607             free(gameInfo.resultDetails);
18608       }
18609         gameInfo.resultDetails = savedDetails[storedGames];
18610         forwardMostMove = currentMove + nrMoves;
18611 }
18612
18613 Boolean
18614 PopTail (Boolean annotate)
18615 {
18616         if(appData.icsActive) return FALSE; // only in local mode
18617         if(!storedGames) return FALSE; // sanity
18618         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18619
18620         PopInner(annotate);
18621         if(currentMove < forwardMostMove) ForwardEvent(); else
18622         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18623
18624         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18625         return TRUE;
18626 }
18627
18628 void
18629 CleanupTail ()
18630 {       // remove all shelved variations
18631         int i;
18632         for(i=0; i<storedGames; i++) {
18633             if(savedDetails[i])
18634                 free(savedDetails[i]);
18635             savedDetails[i] = NULL;
18636         }
18637         for(i=framePtr; i<MAX_MOVES; i++) {
18638                 if(commentList[i]) free(commentList[i]);
18639                 commentList[i] = NULL;
18640         }
18641         framePtr = MAX_MOVES-1;
18642         storedGames = 0;
18643 }
18644
18645 void
18646 LoadVariation (int index, char *text)
18647 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18648         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18649         int level = 0, move;
18650
18651         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18652         // first find outermost bracketing variation
18653         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18654             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18655                 if(*p == '{') wait = '}'; else
18656                 if(*p == '[') wait = ']'; else
18657                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18658                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18659             }
18660             if(*p == wait) wait = NULLCHAR; // closing ]} found
18661             p++;
18662         }
18663         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18664         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18665         end[1] = NULLCHAR; // clip off comment beyond variation
18666         ToNrEvent(currentMove-1);
18667         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18668         // kludge: use ParsePV() to append variation to game
18669         move = currentMove;
18670         ParsePV(start, TRUE, TRUE);
18671         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18672         ClearPremoveHighlights();
18673         CommentPopDown();
18674         ToNrEvent(currentMove+1);
18675 }
18676
18677 void
18678 LoadTheme ()
18679 {
18680     char *p, *q, buf[MSG_SIZ];
18681     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18682         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18683         ParseArgsFromString(buf);
18684         ActivateTheme(TRUE); // also redo colors
18685         return;
18686     }
18687     p = nickName;
18688     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18689     {
18690         int len;
18691         q = appData.themeNames;
18692         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18693       if(appData.useBitmaps) {
18694         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18695                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18696                 appData.liteBackTextureMode,
18697                 appData.darkBackTextureMode );
18698       } else {
18699         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18700                 Col2Text(2),   // lightSquareColor
18701                 Col2Text(3) ); // darkSquareColor
18702       }
18703       if(appData.useBorder) {
18704         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18705                 appData.border);
18706       } else {
18707         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18708       }
18709       if(appData.useFont) {
18710         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18711                 appData.renderPiecesWithFont,
18712                 appData.fontToPieceTable,
18713                 Col2Text(9),    // appData.fontBackColorWhite
18714                 Col2Text(10) ); // appData.fontForeColorBlack
18715       } else {
18716         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18717                 appData.pieceDirectory);
18718         if(!appData.pieceDirectory[0])
18719           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18720                 Col2Text(0),   // whitePieceColor
18721                 Col2Text(1) ); // blackPieceColor
18722       }
18723       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18724                 Col2Text(4),   // highlightSquareColor
18725                 Col2Text(5) ); // premoveHighlightColor
18726         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18727         if(insert != q) insert[-1] = NULLCHAR;
18728         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18729         if(q)   free(q);
18730     }
18731     ActivateTheme(FALSE);
18732 }