Fix chosen WB v1 install
[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 SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks, handSize, handOffsets;
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 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first 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_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode, tryNr;
917
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
922
923 void
924 FloatToFront(char **list, char *engineLine)
925 {
926     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
927     int i=0;
928     if(appData.recentEngines <= 0) return;
929     TidyProgramName(engineLine, "localhost", tidy+1);
930     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931     strncpy(buf+1, *list, MSG_SIZ-50);
932     if(p = strstr(buf, tidy)) { // tidy name appears in list
933         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934         while(*p++ = *++q); // squeeze out
935     }
936     strcat(tidy, buf+1); // put list behind tidy name
937     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939     ASSIGN(*list, tidy+1);
940 }
941
942 void
943 SaveEngineList ()
944 {
945         FILE *f;
946         if(*engineListFile && (f = fopen(engineListFile, "w"))) {
947           fprintf(f, "-firstChessProgramNames {%s}\n", firstChessProgramNames);
948           fclose(f);
949         }
950 }
951
952 void
953 AddToEngineList (int i)
954 {
955         int len;
956         char quote, buf[MSG_SIZ];
957         char *q = firstChessProgramNames, *p = newEngineCommand;
958         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
959         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
960         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
961                         quote, p, quote, appData.directory[i],
962                         useNick ? " -fn \"" : "",
963                         useNick ? nickName : "",
964                         useNick ? "\"" : "",
965                         v1 ? " -firstProtocolVersion 1" : "",
966                         hasBook ? "" : " -fNoOwnBookUCI",
967                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
968                         storeVariant ? " -variant " : "",
969                         storeVariant ? VariantName(gameInfo.variant) : "");
970         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
971         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
972         if(insert != q) insert[-1] = NULLCHAR;
973         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
974         if(q)   free(q);
975         SaveEngineList();
976         FloatToFront(&appData.recentEngineList, buf);
977         ASSIGN(currentEngine[i], buf);
978 }
979
980 void
981 LoadEngine ()
982 {
983     int i;
984     if(WaitForEngine(savCps, LoadEngine)) return;
985     if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
986     if(tryNr) v1 |= (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
987     CommonEngineInit(); // recalculate time odds
988     if(gameInfo.variant != StringToVariant(appData.variant)) {
989         // we changed variant when loading the engine; this forces us to reset
990         Reset(TRUE, savCps != &first);
991         oldMode = BeginningOfGame; // to prevent restoring old mode
992     }
993     InitChessProgram(savCps, FALSE);
994     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
995     DisplayMessage("", "");
996     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
997     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
998     ThawUI();
999     SetGNUMode();
1000     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
1001 }
1002
1003 void
1004 ReplaceEngine (ChessProgramState *cps, int n)
1005 {
1006     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
1007     keepInfo = 1;
1008     if(oldMode != BeginningOfGame) EditGameEvent();
1009     keepInfo = 0;
1010     UnloadEngine(cps);
1011     appData.noChessProgram = FALSE;
1012     appData.clockMode = TRUE;
1013     InitEngine(cps, n);
1014     UpdateLogos(TRUE);
1015     if(n && !tryNr) return; // only startup first engine immediately; second can wait (unless autodetect)
1016     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1017     LoadEngine();
1018 }
1019
1020 static char resetOptions[] =
1021         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1022         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1023         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1024         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1025
1026 void
1027 Load (ChessProgramState *cps, int i)
1028 {
1029     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1030     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1031         ASSIGN(currentEngine[i], engineLine);
1032         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1033         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1034         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1035         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1036         appData.firstProtocolVersion = PROTOVER;
1037         ParseArgsFromString(buf);
1038         SwapEngines(i);
1039         ReplaceEngine(cps, i);
1040         FloatToFront(&appData.recentEngineList, engineLine);
1041         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1042         return;
1043     }
1044     p = engineName;
1045     while(q = strchr(p, SLASH)) p = q+1;
1046     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1047     if(engineDir[0] != NULLCHAR) {
1048         ASSIGN(appData.directory[i], engineDir); p = engineName;
1049     } else if(p != engineName) { // derive directory from engine path, when not given
1050         p[-1] = 0;
1051         ASSIGN(appData.directory[i], engineName);
1052         p[-1] = SLASH;
1053         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1054     } else { ASSIGN(appData.directory[i], "."); }
1055     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1056     if(params[0]) {
1057         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1058         snprintf(command, MSG_SIZ, "%s %s", p, params);
1059         p = command;
1060     }
1061     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1062     ASSIGN(appData.chessProgram[i], p);
1063     if(isUCI == 3) tryNr = 1, isUCI = 0; // auto-detect
1064     appData.isUCI[i] = isUCI;
1065     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1066     appData.hasOwnBookUCI[i] = hasBook;
1067     if(!nickName[0]) useNick = FALSE;
1068     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1069     safeStrCpy(newEngineCommand, p, MSG_SIZ);
1070     ReplaceEngine(cps, i);
1071 }
1072
1073 void
1074 InitTimeControls ()
1075 {
1076     int matched, min, sec;
1077     /*
1078      * Parse timeControl resource
1079      */
1080     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1081                           appData.movesPerSession)) {
1082         char buf[MSG_SIZ];
1083         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1084         DisplayFatalError(buf, 0, 2);
1085     }
1086
1087     /*
1088      * Parse searchTime resource
1089      */
1090     if (*appData.searchTime != NULLCHAR) {
1091         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1092         if (matched == 1) {
1093             searchTime = min * 60;
1094         } else if (matched == 2) {
1095             searchTime = min * 60 + sec;
1096         } else {
1097             char buf[MSG_SIZ];
1098             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1099             DisplayFatalError(buf, 0, 2);
1100         }
1101     }
1102 }
1103
1104 void
1105 InitBackEnd1 ()
1106 {
1107
1108     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1109     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1110
1111     GetTimeMark(&programStartTime);
1112     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1113     appData.seedBase = random() + (random()<<15);
1114     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1115
1116     ClearProgramStats();
1117     programStats.ok_to_send = 1;
1118     programStats.seen_stat = 0;
1119
1120     /*
1121      * Initialize game list
1122      */
1123     ListNew(&gameList);
1124
1125
1126     /*
1127      * Internet chess server status
1128      */
1129     if (appData.icsActive) {
1130         appData.matchMode = FALSE;
1131         appData.matchGames = 0;
1132 #if ZIPPY
1133         appData.noChessProgram = !appData.zippyPlay;
1134 #else
1135         appData.zippyPlay = FALSE;
1136         appData.zippyTalk = FALSE;
1137         appData.noChessProgram = TRUE;
1138 #endif
1139         if (*appData.icsHelper != NULLCHAR) {
1140             appData.useTelnet = TRUE;
1141             appData.telnetProgram = appData.icsHelper;
1142         }
1143     } else {
1144         appData.zippyTalk = appData.zippyPlay = FALSE;
1145     }
1146
1147     /* [AS] Initialize pv info list [HGM] and game state */
1148     {
1149         int i, j;
1150
1151         for( i=0; i<=framePtr; i++ ) {
1152             pvInfoList[i].depth = -1;
1153             boards[i][EP_STATUS] = EP_NONE;
1154             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1155         }
1156     }
1157
1158     InitTimeControls();
1159
1160     /* [AS] Adjudication threshold */
1161     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1162
1163     InitEngine(&first, 0);
1164     InitEngine(&second, 1);
1165     CommonEngineInit();
1166
1167     pairing.which = "pairing"; // pairing engine
1168     pairing.pr = NoProc;
1169     pairing.isr = NULL;
1170     pairing.program = appData.pairingEngine;
1171     pairing.host = "localhost";
1172     pairing.dir = ".";
1173
1174     if (appData.icsActive) {
1175         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1176     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1177         appData.clockMode = FALSE;
1178         first.sendTime = second.sendTime = 0;
1179     }
1180
1181 #if ZIPPY
1182     /* Override some settings from environment variables, for backward
1183        compatibility.  Unfortunately it's not feasible to have the env
1184        vars just set defaults, at least in xboard.  Ugh.
1185     */
1186     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1187       ZippyInit();
1188     }
1189 #endif
1190
1191     if (!appData.icsActive) {
1192       char buf[MSG_SIZ];
1193       int len;
1194
1195       /* Check for variants that are supported only in ICS mode,
1196          or not at all.  Some that are accepted here nevertheless
1197          have bugs; see comments below.
1198       */
1199       VariantClass variant = StringToVariant(appData.variant);
1200       switch (variant) {
1201       case VariantBughouse:     /* need four players and two boards */
1202       case VariantKriegspiel:   /* need to hide pieces and move details */
1203         /* case VariantFischeRandom: (Fabien: moved below) */
1204         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantUnknown:
1212       case VariantLoadable:
1213       case Variant29:
1214       case Variant30:
1215       case Variant31:
1216       case Variant32:
1217       case Variant33:
1218       case Variant34:
1219       case Variant35:
1220       case Variant36:
1221       default:
1222         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1223         if( (len >= MSG_SIZ) && appData.debugMode )
1224           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1225
1226         DisplayFatalError(buf, 0, 2);
1227         return;
1228
1229       case VariantNormal:     /* definitely works! */
1230         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1231           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1232           return;
1233         }
1234       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1235       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1236       case VariantGothic:     /* [HGM] should work */
1237       case VariantCapablanca: /* [HGM] should work */
1238       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1239       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1240       case VariantChu:        /* [HGM] experimental */
1241       case VariantKnightmate: /* [HGM] should work */
1242       case VariantCylinder:   /* [HGM] untested */
1243       case VariantFalcon:     /* [HGM] untested */
1244       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1245                                  offboard interposition not understood */
1246       case VariantWildCastle: /* pieces not automatically shuffled */
1247       case VariantNoCastle:   /* pieces not automatically shuffled */
1248       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1249       case VariantLosers:     /* should work except for win condition,
1250                                  and doesn't know captures are mandatory */
1251       case VariantSuicide:    /* should work except for win condition,
1252                                  and doesn't know captures are mandatory */
1253       case VariantGiveaway:   /* should work except for win condition,
1254                                  and doesn't know captures are mandatory */
1255       case VariantTwoKings:   /* should work */
1256       case VariantAtomic:     /* should work except for win condition */
1257       case Variant3Check:     /* should work except for win condition */
1258       case VariantShatranj:   /* should work except for all win conditions */
1259       case VariantMakruk:     /* should work except for draw countdown */
1260       case VariantASEAN :     /* should work except for draw countdown */
1261       case VariantBerolina:   /* might work if TestLegality is off */
1262       case VariantCapaRandom: /* should work */
1263       case VariantJanus:      /* should work */
1264       case VariantSuper:      /* experimental */
1265       case VariantGreat:      /* experimental, requires legality testing to be off */
1266       case VariantSChess:     /* S-Chess, should work */
1267       case VariantGrand:      /* should work */
1268       case VariantSpartan:    /* should work */
1269       case VariantLion:       /* should work */
1270       case VariantChuChess:   /* should work */
1271         break;
1272       }
1273     }
1274
1275 }
1276
1277 int
1278 NextIntegerFromString (char ** str, long * value)
1279 {
1280     int result = -1;
1281     char * s = *str;
1282
1283     while( *s == ' ' || *s == '\t' ) {
1284         s++;
1285     }
1286
1287     *value = 0;
1288
1289     if( *s >= '0' && *s <= '9' ) {
1290         while( *s >= '0' && *s <= '9' ) {
1291             *value = *value * 10 + (*s - '0');
1292             s++;
1293         }
1294
1295         result = 0;
1296     }
1297
1298     *str = s;
1299
1300     return result;
1301 }
1302
1303 int
1304 NextTimeControlFromString (char ** str, long * value)
1305 {
1306     long temp;
1307     int result = NextIntegerFromString( str, &temp );
1308
1309     if( result == 0 ) {
1310         *value = temp * 60; /* Minutes */
1311         if( **str == ':' ) {
1312             (*str)++;
1313             result = NextIntegerFromString( str, &temp );
1314             *value += temp; /* Seconds */
1315         }
1316     }
1317
1318     return result;
1319 }
1320
1321 int
1322 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1323 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1324     int result = -1, type = 0; long temp, temp2;
1325
1326     if(**str != ':') return -1; // old params remain in force!
1327     (*str)++;
1328     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1329     if( NextIntegerFromString( str, &temp ) ) return -1;
1330     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1331
1332     if(**str != '/') {
1333         /* time only: incremental or sudden-death time control */
1334         if(**str == '+') { /* increment follows; read it */
1335             (*str)++;
1336             if(**str == '!') type = *(*str)++; // Bronstein TC
1337             if(result = NextIntegerFromString( str, &temp2)) return -1;
1338             *inc = temp2 * 1000;
1339             if(**str == '.') { // read fraction of increment
1340                 char *start = ++(*str);
1341                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1342                 temp2 *= 1000;
1343                 while(start++ < *str) temp2 /= 10;
1344                 *inc += temp2;
1345             }
1346         } else *inc = 0;
1347         *moves = 0; *tc = temp * 1000; *incType = type;
1348         return 0;
1349     }
1350
1351     (*str)++; /* classical time control */
1352     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1353
1354     if(result == 0) {
1355         *moves = temp;
1356         *tc    = temp2 * 1000;
1357         *inc   = 0;
1358         *incType = type;
1359     }
1360     return result;
1361 }
1362
1363 int
1364 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1365 {   /* [HGM] get time to add from the multi-session time-control string */
1366     int incType, moves=1; /* kludge to force reading of first session */
1367     long time, increment;
1368     char *s = tcString;
1369
1370     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1371     do {
1372         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1373         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1374         if(movenr == -1) return time;    /* last move before new session     */
1375         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1376         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1377         if(!moves) return increment;     /* current session is incremental   */
1378         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1379     } while(movenr >= -1);               /* try again for next session       */
1380
1381     return 0; // no new time quota on this move
1382 }
1383
1384 int
1385 ParseTimeControl (char *tc, float ti, int mps)
1386 {
1387   long tc1;
1388   long tc2;
1389   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1390   int min, sec=0;
1391
1392   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1393   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1394       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1395   if(ti > 0) {
1396
1397     if(mps)
1398       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1399     else
1400       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1401   } else {
1402     if(mps)
1403       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1404     else
1405       snprintf(buf, MSG_SIZ, ":%s", mytc);
1406   }
1407   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1408
1409   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1410     return FALSE;
1411   }
1412
1413   if( *tc == '/' ) {
1414     /* Parse second time control */
1415     tc++;
1416
1417     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1418       return FALSE;
1419     }
1420
1421     if( tc2 == 0 ) {
1422       return FALSE;
1423     }
1424
1425     timeControl_2 = tc2 * 1000;
1426   }
1427   else {
1428     timeControl_2 = 0;
1429   }
1430
1431   if( tc1 == 0 ) {
1432     return FALSE;
1433   }
1434
1435   timeControl = tc1 * 1000;
1436
1437   if (ti >= 0) {
1438     timeIncrement = ti * 1000;  /* convert to ms */
1439     movesPerSession = 0;
1440   } else {
1441     timeIncrement = 0;
1442     movesPerSession = mps;
1443   }
1444   return TRUE;
1445 }
1446
1447 void
1448 InitBackEnd2 ()
1449 {
1450     if (appData.debugMode) {
1451 #    ifdef __GIT_VERSION
1452       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1453 #    else
1454       fprintf(debugFP, "Version: %s\n", programVersion);
1455 #    endif
1456     }
1457     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1458
1459     set_cont_sequence(appData.wrapContSeq);
1460     if (appData.matchGames > 0) {
1461         appData.matchMode = TRUE;
1462     } else if (appData.matchMode) {
1463         appData.matchGames = 1;
1464     }
1465     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1466         appData.matchGames = appData.sameColorGames;
1467     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1468         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1469         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1470     }
1471     Reset(TRUE, FALSE);
1472     if (appData.noChessProgram || first.protocolVersion == 1) {
1473       InitBackEnd3();
1474     } else {
1475       /* kludge: allow timeout for initial "feature" commands */
1476       FreezeUI();
1477       DisplayMessage("", _("Starting chess program"));
1478       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1479     }
1480 }
1481
1482 int
1483 CalculateIndex (int index, int gameNr)
1484 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1485     int res;
1486     if(index > 0) return index; // fixed nmber
1487     if(index == 0) return 1;
1488     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1489     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1490     return res;
1491 }
1492
1493 int
1494 LoadGameOrPosition (int gameNr)
1495 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1496     if (*appData.loadGameFile != NULLCHAR) {
1497         if (!LoadGameFromFile(appData.loadGameFile,
1498                 CalculateIndex(appData.loadGameIndex, gameNr),
1499                               appData.loadGameFile, FALSE)) {
1500             DisplayFatalError(_("Bad game file"), 0, 1);
1501             return 0;
1502         }
1503     } else if (*appData.loadPositionFile != NULLCHAR) {
1504         if (!LoadPositionFromFile(appData.loadPositionFile,
1505                 CalculateIndex(appData.loadPositionIndex, gameNr),
1506                                   appData.loadPositionFile)) {
1507             DisplayFatalError(_("Bad position file"), 0, 1);
1508             return 0;
1509         }
1510     }
1511     return 1;
1512 }
1513
1514 void
1515 ReserveGame (int gameNr, char resChar)
1516 {
1517     FILE *tf = fopen(appData.tourneyFile, "r+");
1518     char *p, *q, c, buf[MSG_SIZ];
1519     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1520     safeStrCpy(buf, lastMsg, MSG_SIZ);
1521     DisplayMessage(_("Pick new game"), "");
1522     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1523     ParseArgsFromFile(tf);
1524     p = q = appData.results;
1525     if(appData.debugMode) {
1526       char *r = appData.participants;
1527       fprintf(debugFP, "results = '%s'\n", p);
1528       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1529       fprintf(debugFP, "\n");
1530     }
1531     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1532     nextGame = q - p;
1533     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1534     safeStrCpy(q, p, strlen(p) + 2);
1535     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1536     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1537     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1538         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1539         q[nextGame] = '*';
1540     }
1541     fseek(tf, -(strlen(p)+4), SEEK_END);
1542     c = fgetc(tf);
1543     if(c != '"') // depending on DOS or Unix line endings we can be one off
1544          fseek(tf, -(strlen(p)+2), SEEK_END);
1545     else fseek(tf, -(strlen(p)+3), SEEK_END);
1546     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1547     DisplayMessage(buf, "");
1548     free(p); appData.results = q;
1549     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1550        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1551       int round = appData.defaultMatchGames * appData.tourneyType;
1552       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1553          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1554         UnloadEngine(&first);  // next game belongs to other pairing;
1555         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1556     }
1557     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1558 }
1559
1560 void
1561 MatchEvent (int mode)
1562 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1563         int dummy;
1564         if(matchMode) { // already in match mode: switch it off
1565             abortMatch = TRUE;
1566             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1567             return;
1568         }
1569 //      if(gameMode != BeginningOfGame) {
1570 //          DisplayError(_("You can only start a match from the initial position."), 0);
1571 //          return;
1572 //      }
1573         abortMatch = FALSE;
1574         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1575         /* Set up machine vs. machine match */
1576         nextGame = 0;
1577         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1578         if(appData.tourneyFile[0]) {
1579             ReserveGame(-1, 0);
1580             if(nextGame > appData.matchGames) {
1581                 char buf[MSG_SIZ];
1582                 if(strchr(appData.results, '*') == NULL) {
1583                     FILE *f;
1584                     appData.tourneyCycles++;
1585                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1586                         fclose(f);
1587                         NextTourneyGame(-1, &dummy);
1588                         ReserveGame(-1, 0);
1589                         if(nextGame <= appData.matchGames) {
1590                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1591                             matchMode = mode;
1592                             ScheduleDelayedEvent(NextMatchGame, 10000);
1593                             return;
1594                         }
1595                     }
1596                 }
1597                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1598                 DisplayError(buf, 0);
1599                 appData.tourneyFile[0] = 0;
1600                 return;
1601             }
1602         } else
1603         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1604             DisplayFatalError(_("Can't have a match with no chess programs"),
1605                               0, 2);
1606             return;
1607         }
1608         matchMode = mode;
1609         matchGame = roundNr = 1;
1610         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1611         NextMatchGame();
1612 }
1613
1614 void
1615 InitBackEnd3 P((void))
1616 {
1617     GameMode initialMode;
1618     char buf[MSG_SIZ];
1619     int err, len;
1620
1621     ParseFeatures(appData.features[0], &first);
1622     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1623        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1624         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1625        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1626        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1627         char c, *q = first.variants, *p = strchr(q, ',');
1628         if(p) *p = NULLCHAR;
1629         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1630             int w, h, s;
1631             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1632                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1633             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1634             Reset(TRUE, FALSE);         // and re-initialize
1635         }
1636         if(p) *p = ',';
1637     }
1638
1639     InitChessProgram(&first, startedFromSetupPosition);
1640
1641     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1642         free(programVersion);
1643         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1644         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1645         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1646     }
1647
1648     if (appData.icsActive) {
1649 #ifdef WIN32
1650         /* [DM] Make a console window if needed [HGM] merged ifs */
1651         ConsoleCreate();
1652 #endif
1653         err = establish();
1654         if (err != 0)
1655           {
1656             if (*appData.icsCommPort != NULLCHAR)
1657               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1658                              appData.icsCommPort);
1659             else
1660               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1661                         appData.icsHost, appData.icsPort);
1662
1663             if( (len >= MSG_SIZ) && appData.debugMode )
1664               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1665
1666             DisplayFatalError(buf, err, 1);
1667             return;
1668         }
1669         SetICSMode();
1670         telnetISR =
1671           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1672         fromUserISR =
1673           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1674         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1675             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1676     } else if (appData.noChessProgram) {
1677         SetNCPMode();
1678     } else {
1679         SetGNUMode();
1680     }
1681
1682     if (*appData.cmailGameName != NULLCHAR) {
1683         SetCmailMode();
1684         OpenLoopback(&cmailPR);
1685         cmailISR =
1686           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1687     }
1688
1689     ThawUI();
1690     DisplayMessage("", "");
1691     if (StrCaseCmp(appData.initialMode, "") == 0) {
1692       initialMode = BeginningOfGame;
1693       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1694         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1695         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1696         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1697         ModeHighlight();
1698       }
1699     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1700       initialMode = TwoMachinesPlay;
1701     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1702       initialMode = AnalyzeFile;
1703     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1704       initialMode = AnalyzeMode;
1705     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1706       initialMode = MachinePlaysWhite;
1707     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1708       initialMode = MachinePlaysBlack;
1709     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1710       initialMode = EditGame;
1711     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1712       initialMode = EditPosition;
1713     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1714       initialMode = Training;
1715     } else {
1716       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1717       if( (len >= MSG_SIZ) && appData.debugMode )
1718         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1719
1720       DisplayFatalError(buf, 0, 2);
1721       return;
1722     }
1723
1724     if (appData.matchMode) {
1725         if(appData.tourneyFile[0]) { // start tourney from command line
1726             FILE *f;
1727             if(f = fopen(appData.tourneyFile, "r")) {
1728                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1729                 fclose(f);
1730                 appData.clockMode = TRUE;
1731                 SetGNUMode();
1732             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1733         }
1734         MatchEvent(TRUE);
1735     } else if (*appData.cmailGameName != NULLCHAR) {
1736         /* Set up cmail mode */
1737         ReloadCmailMsgEvent(TRUE);
1738     } else {
1739         /* Set up other modes */
1740         if (initialMode == AnalyzeFile) {
1741           if (*appData.loadGameFile == NULLCHAR) {
1742             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1743             return;
1744           }
1745         }
1746         if (*appData.loadGameFile != NULLCHAR) {
1747             (void) LoadGameFromFile(appData.loadGameFile,
1748                                     appData.loadGameIndex,
1749                                     appData.loadGameFile, TRUE);
1750         } else if (*appData.loadPositionFile != NULLCHAR) {
1751             (void) LoadPositionFromFile(appData.loadPositionFile,
1752                                         appData.loadPositionIndex,
1753                                         appData.loadPositionFile);
1754             /* [HGM] try to make self-starting even after FEN load */
1755             /* to allow automatic setup of fairy variants with wtm */
1756             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1757                 gameMode = BeginningOfGame;
1758                 setboardSpoiledMachineBlack = 1;
1759             }
1760             /* [HGM] loadPos: make that every new game uses the setup */
1761             /* from file as long as we do not switch variant          */
1762             if(!blackPlaysFirst) {
1763                 startedFromPositionFile = TRUE;
1764                 CopyBoard(filePosition, boards[0]);
1765                 CopyBoard(initialPosition, boards[0]);
1766             }
1767         } else if(*appData.fen != NULLCHAR) {
1768             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1769                 startedFromPositionFile = TRUE;
1770                 Reset(TRUE, TRUE);
1771             }
1772         }
1773         if (initialMode == AnalyzeMode) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1776             return;
1777           }
1778           if (appData.icsActive) {
1779             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1780             return;
1781           }
1782           AnalyzeModeEvent();
1783         } else if (initialMode == AnalyzeFile) {
1784           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1785           ShowThinkingEvent();
1786           AnalyzeFileEvent();
1787           AnalysisPeriodicEvent(1);
1788         } else if (initialMode == MachinePlaysWhite) {
1789           if (appData.noChessProgram) {
1790             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1791                               0, 2);
1792             return;
1793           }
1794           if (appData.icsActive) {
1795             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1796                               0, 2);
1797             return;
1798           }
1799           MachineWhiteEvent();
1800         } else if (initialMode == MachinePlaysBlack) {
1801           if (appData.noChessProgram) {
1802             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1803                               0, 2);
1804             return;
1805           }
1806           if (appData.icsActive) {
1807             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1808                               0, 2);
1809             return;
1810           }
1811           MachineBlackEvent();
1812         } else if (initialMode == TwoMachinesPlay) {
1813           if (appData.noChessProgram) {
1814             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1815                               0, 2);
1816             return;
1817           }
1818           if (appData.icsActive) {
1819             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1820                               0, 2);
1821             return;
1822           }
1823           TwoMachinesEvent();
1824         } else if (initialMode == EditGame) {
1825           EditGameEvent();
1826         } else if (initialMode == EditPosition) {
1827           EditPositionEvent();
1828         } else if (initialMode == Training) {
1829           if (*appData.loadGameFile == NULLCHAR) {
1830             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1831             return;
1832           }
1833           TrainingEvent();
1834         }
1835     }
1836 }
1837
1838 void
1839 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1840 {
1841     DisplayBook(current+1);
1842
1843     MoveHistorySet( movelist, first, last, current, pvInfoList );
1844
1845     EvalGraphSet( first, last, current, pvInfoList );
1846
1847     MakeEngineOutputTitle();
1848 }
1849
1850 /*
1851  * Establish will establish a contact to a remote host.port.
1852  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1853  *  used to talk to the host.
1854  * Returns 0 if okay, error code if not.
1855  */
1856 int
1857 establish ()
1858 {
1859     char buf[MSG_SIZ];
1860
1861     if (*appData.icsCommPort != NULLCHAR) {
1862         /* Talk to the host through a serial comm port */
1863         return OpenCommPort(appData.icsCommPort, &icsPR);
1864
1865     } else if (*appData.gateway != NULLCHAR) {
1866         if (*appData.remoteShell == NULLCHAR) {
1867             /* Use the rcmd protocol to run telnet program on a gateway host */
1868             snprintf(buf, sizeof(buf), "%s %s %s",
1869                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1870             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1871
1872         } else {
1873             /* Use the rsh program to run telnet program on a gateway host */
1874             if (*appData.remoteUser == NULLCHAR) {
1875                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1876                         appData.gateway, appData.telnetProgram,
1877                         appData.icsHost, appData.icsPort);
1878             } else {
1879                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1880                         appData.remoteShell, appData.gateway,
1881                         appData.remoteUser, appData.telnetProgram,
1882                         appData.icsHost, appData.icsPort);
1883             }
1884             return StartChildProcess(buf, "", &icsPR);
1885
1886         }
1887     } else if (appData.useTelnet) {
1888         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1889
1890     } else {
1891         /* TCP socket interface differs somewhat between
1892            Unix and NT; handle details in the front end.
1893            */
1894         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1895     }
1896 }
1897
1898 void
1899 EscapeExpand (char *p, char *q)
1900 {       // [HGM] initstring: routine to shape up string arguments
1901         while(*p++ = *q++) if(p[-1] == '\\')
1902             switch(*q++) {
1903                 case 'n': p[-1] = '\n'; break;
1904                 case 'r': p[-1] = '\r'; break;
1905                 case 't': p[-1] = '\t'; break;
1906                 case '\\': p[-1] = '\\'; break;
1907                 case 0: *p = 0; return;
1908                 default: p[-1] = q[-1]; break;
1909             }
1910 }
1911
1912 void
1913 show_bytes (FILE *fp, char *buf, int count)
1914 {
1915     while (count--) {
1916         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1917             fprintf(fp, "\\%03o", *buf & 0xff);
1918         } else {
1919             putc(*buf, fp);
1920         }
1921         buf++;
1922     }
1923     fflush(fp);
1924 }
1925
1926 /* Returns an errno value */
1927 int
1928 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1929 {
1930     char buf[8192], *p, *q, *buflim;
1931     int left, newcount, outcount;
1932
1933     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1934         *appData.gateway != NULLCHAR) {
1935         if (appData.debugMode) {
1936             fprintf(debugFP, ">ICS: ");
1937             show_bytes(debugFP, message, count);
1938             fprintf(debugFP, "\n");
1939         }
1940         return OutputToProcess(pr, message, count, outError);
1941     }
1942
1943     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1944     p = message;
1945     q = buf;
1946     left = count;
1947     newcount = 0;
1948     while (left) {
1949         if (q >= buflim) {
1950             if (appData.debugMode) {
1951                 fprintf(debugFP, ">ICS: ");
1952                 show_bytes(debugFP, buf, newcount);
1953                 fprintf(debugFP, "\n");
1954             }
1955             outcount = OutputToProcess(pr, buf, newcount, outError);
1956             if (outcount < newcount) return -1; /* to be sure */
1957             q = buf;
1958             newcount = 0;
1959         }
1960         if (*p == '\n') {
1961             *q++ = '\r';
1962             newcount++;
1963         } else if (((unsigned char) *p) == TN_IAC) {
1964             *q++ = (char) TN_IAC;
1965             newcount ++;
1966         }
1967         *q++ = *p++;
1968         newcount++;
1969         left--;
1970     }
1971     if (appData.debugMode) {
1972         fprintf(debugFP, ">ICS: ");
1973         show_bytes(debugFP, buf, newcount);
1974         fprintf(debugFP, "\n");
1975     }
1976     outcount = OutputToProcess(pr, buf, newcount, outError);
1977     if (outcount < newcount) return -1; /* to be sure */
1978     return count;
1979 }
1980
1981 void
1982 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1983 {
1984     int outError, outCount;
1985     static int gotEof = 0;
1986     static FILE *ini;
1987
1988     /* Pass data read from player on to ICS */
1989     if (count > 0) {
1990         gotEof = 0;
1991         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1992         if (outCount < count) {
1993             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1994         }
1995         if(have_sent_ICS_logon == 2) {
1996           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1997             fprintf(ini, "%s", message);
1998             have_sent_ICS_logon = 3;
1999           } else
2000             have_sent_ICS_logon = 1;
2001         } else if(have_sent_ICS_logon == 3) {
2002             fprintf(ini, "%s", message);
2003             fclose(ini);
2004           have_sent_ICS_logon = 1;
2005         }
2006     } else if (count < 0) {
2007         RemoveInputSource(isr);
2008         DisplayFatalError(_("Error reading from keyboard"), error, 1);
2009     } else if (gotEof++ > 0) {
2010         RemoveInputSource(isr);
2011         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2012     }
2013 }
2014
2015 void
2016 KeepAlive ()
2017 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2018     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2019     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2020     SendToICS("date\n");
2021     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2022 }
2023
2024 /* added routine for printf style output to ics */
2025 void
2026 ics_printf (char *format, ...)
2027 {
2028     char buffer[MSG_SIZ];
2029     va_list args;
2030
2031     va_start(args, format);
2032     vsnprintf(buffer, sizeof(buffer), format, args);
2033     buffer[sizeof(buffer)-1] = '\0';
2034     SendToICS(buffer);
2035     va_end(args);
2036 }
2037
2038 void
2039 SendToICS (char *s)
2040 {
2041     int count, outCount, outError;
2042
2043     if (icsPR == NoProc) return;
2044
2045     count = strlen(s);
2046     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2047     if (outCount < count) {
2048         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2049     }
2050 }
2051
2052 /* This is used for sending logon scripts to the ICS. Sending
2053    without a delay causes problems when using timestamp on ICC
2054    (at least on my machine). */
2055 void
2056 SendToICSDelayed (char *s, long msdelay)
2057 {
2058     int count, outCount, outError;
2059
2060     if (icsPR == NoProc) return;
2061
2062     count = strlen(s);
2063     if (appData.debugMode) {
2064         fprintf(debugFP, ">ICS: ");
2065         show_bytes(debugFP, s, count);
2066         fprintf(debugFP, "\n");
2067     }
2068     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2069                                       msdelay);
2070     if (outCount < count) {
2071         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2072     }
2073 }
2074
2075
2076 /* Remove all highlighting escape sequences in s
2077    Also deletes any suffix starting with '('
2078    */
2079 char *
2080 StripHighlightAndTitle (char *s)
2081 {
2082     static char retbuf[MSG_SIZ];
2083     char *p = retbuf;
2084
2085     while (*s != NULLCHAR) {
2086         while (*s == '\033') {
2087             while (*s != NULLCHAR && !isalpha(*s)) s++;
2088             if (*s != NULLCHAR) s++;
2089         }
2090         while (*s != NULLCHAR && *s != '\033') {
2091             if (*s == '(' || *s == '[') {
2092                 *p = NULLCHAR;
2093                 return retbuf;
2094             }
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 /* Remove all highlighting escape sequences in s */
2103 char *
2104 StripHighlight (char *s)
2105 {
2106     static char retbuf[MSG_SIZ];
2107     char *p = retbuf;
2108
2109     while (*s != NULLCHAR) {
2110         while (*s == '\033') {
2111             while (*s != NULLCHAR && !isalpha(*s)) s++;
2112             if (*s != NULLCHAR) s++;
2113         }
2114         while (*s != NULLCHAR && *s != '\033') {
2115             *p++ = *s++;
2116         }
2117     }
2118     *p = NULLCHAR;
2119     return retbuf;
2120 }
2121
2122 char engineVariant[MSG_SIZ];
2123 char *variantNames[] = VARIANT_NAMES;
2124 char *
2125 VariantName (VariantClass v)
2126 {
2127     if(v == VariantUnknown || *engineVariant) return engineVariant;
2128     return variantNames[v];
2129 }
2130
2131
2132 /* Identify a variant from the strings the chess servers use or the
2133    PGN Variant tag names we use. */
2134 VariantClass
2135 StringToVariant (char *e)
2136 {
2137     char *p;
2138     int wnum = -1;
2139     VariantClass v = VariantNormal;
2140     int i, found = FALSE;
2141     char buf[MSG_SIZ], c;
2142     int len;
2143
2144     if (!e) return v;
2145
2146     /* [HGM] skip over optional board-size prefixes */
2147     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2148         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2149         while( *e++ != '_');
2150     }
2151
2152     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2153         v = VariantNormal;
2154         found = TRUE;
2155     } else
2156     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2157       if (p = StrCaseStr(e, variantNames[i])) {
2158         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2159         v = (VariantClass) i;
2160         found = TRUE;
2161         break;
2162       }
2163     }
2164
2165     if (!found) {
2166       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2167           || StrCaseStr(e, "wild/fr")
2168           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2169         v = VariantFischeRandom;
2170       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2171                  (i = 1, p = StrCaseStr(e, "w"))) {
2172         p += i;
2173         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2174         if (isdigit(*p)) {
2175           wnum = atoi(p);
2176         } else {
2177           wnum = -1;
2178         }
2179         switch (wnum) {
2180         case 0: /* FICS only, actually */
2181         case 1:
2182           /* Castling legal even if K starts on d-file */
2183           v = VariantWildCastle;
2184           break;
2185         case 2:
2186         case 3:
2187         case 4:
2188           /* Castling illegal even if K & R happen to start in
2189              normal positions. */
2190           v = VariantNoCastle;
2191           break;
2192         case 5:
2193         case 7:
2194         case 8:
2195         case 10:
2196         case 11:
2197         case 12:
2198         case 13:
2199         case 14:
2200         case 15:
2201         case 18:
2202         case 19:
2203           /* Castling legal iff K & R start in normal positions */
2204           v = VariantNormal;
2205           break;
2206         case 6:
2207         case 20:
2208         case 21:
2209           /* Special wilds for position setup; unclear what to do here */
2210           v = VariantLoadable;
2211           break;
2212         case 9:
2213           /* Bizarre ICC game */
2214           v = VariantTwoKings;
2215           break;
2216         case 16:
2217           v = VariantKriegspiel;
2218           break;
2219         case 17:
2220           v = VariantLosers;
2221           break;
2222         case 22:
2223           v = VariantFischeRandom;
2224           break;
2225         case 23:
2226           v = VariantCrazyhouse;
2227           break;
2228         case 24:
2229           v = VariantBughouse;
2230           break;
2231         case 25:
2232           v = Variant3Check;
2233           break;
2234         case 26:
2235           /* Not quite the same as FICS suicide! */
2236           v = VariantGiveaway;
2237           break;
2238         case 27:
2239           v = VariantAtomic;
2240           break;
2241         case 28:
2242           v = VariantShatranj;
2243           break;
2244
2245         /* Temporary names for future ICC types.  The name *will* change in
2246            the next xboard/WinBoard release after ICC defines it. */
2247         case 29:
2248           v = Variant29;
2249           break;
2250         case 30:
2251           v = Variant30;
2252           break;
2253         case 31:
2254           v = Variant31;
2255           break;
2256         case 32:
2257           v = Variant32;
2258           break;
2259         case 33:
2260           v = Variant33;
2261           break;
2262         case 34:
2263           v = Variant34;
2264           break;
2265         case 35:
2266           v = Variant35;
2267           break;
2268         case 36:
2269           v = Variant36;
2270           break;
2271         case 37:
2272           v = VariantShogi;
2273           break;
2274         case 38:
2275           v = VariantXiangqi;
2276           break;
2277         case 39:
2278           v = VariantCourier;
2279           break;
2280         case 40:
2281           v = VariantGothic;
2282           break;
2283         case 41:
2284           v = VariantCapablanca;
2285           break;
2286         case 42:
2287           v = VariantKnightmate;
2288           break;
2289         case 43:
2290           v = VariantFairy;
2291           break;
2292         case 44:
2293           v = VariantCylinder;
2294           break;
2295         case 45:
2296           v = VariantFalcon;
2297           break;
2298         case 46:
2299           v = VariantCapaRandom;
2300           break;
2301         case 47:
2302           v = VariantBerolina;
2303           break;
2304         case 48:
2305           v = VariantJanus;
2306           break;
2307         case 49:
2308           v = VariantSuper;
2309           break;
2310         case 50:
2311           v = VariantGreat;
2312           break;
2313         case -1:
2314           /* Found "wild" or "w" in the string but no number;
2315              must assume it's normal chess. */
2316           v = VariantNormal;
2317           break;
2318         default:
2319           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2320           if( (len >= MSG_SIZ) && appData.debugMode )
2321             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2322
2323           DisplayError(buf, 0);
2324           v = VariantUnknown;
2325           break;
2326         }
2327       }
2328     }
2329     if (appData.debugMode) {
2330       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2331               e, wnum, VariantName(v));
2332     }
2333     return v;
2334 }
2335
2336 static int leftover_start = 0, leftover_len = 0;
2337 char star_match[STAR_MATCH_N][MSG_SIZ];
2338
2339 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2340    advance *index beyond it, and set leftover_start to the new value of
2341    *index; else return FALSE.  If pattern contains the character '*', it
2342    matches any sequence of characters not containing '\r', '\n', or the
2343    character following the '*' (if any), and the matched sequence(s) are
2344    copied into star_match.
2345    */
2346 int
2347 looking_at ( char *buf, int *index, char *pattern)
2348 {
2349     char *bufp = &buf[*index], *patternp = pattern;
2350     int star_count = 0;
2351     char *matchp = star_match[0];
2352
2353     for (;;) {
2354         if (*patternp == NULLCHAR) {
2355             *index = leftover_start = bufp - buf;
2356             *matchp = NULLCHAR;
2357             return TRUE;
2358         }
2359         if (*bufp == NULLCHAR) return FALSE;
2360         if (*patternp == '*') {
2361             if (*bufp == *(patternp + 1)) {
2362                 *matchp = NULLCHAR;
2363                 matchp = star_match[++star_count];
2364                 patternp += 2;
2365                 bufp++;
2366                 continue;
2367             } else if (*bufp == '\n' || *bufp == '\r') {
2368                 patternp++;
2369                 if (*patternp == NULLCHAR)
2370                   continue;
2371                 else
2372                   return FALSE;
2373             } else {
2374                 *matchp++ = *bufp++;
2375                 continue;
2376             }
2377         }
2378         if (*patternp != *bufp) return FALSE;
2379         patternp++;
2380         bufp++;
2381     }
2382 }
2383
2384 void
2385 SendToPlayer (char *data, int length)
2386 {
2387     int error, outCount;
2388     outCount = OutputToProcess(NoProc, data, length, &error);
2389     if (outCount < length) {
2390         DisplayFatalError(_("Error writing to display"), error, 1);
2391     }
2392 }
2393
2394 void
2395 PackHolding (char packed[], char *holding)
2396 {
2397     char *p = holding;
2398     char *q = packed;
2399     int runlength = 0;
2400     int curr = 9999;
2401     do {
2402         if (*p == curr) {
2403             runlength++;
2404         } else {
2405             switch (runlength) {
2406               case 0:
2407                 break;
2408               case 1:
2409                 *q++ = curr;
2410                 break;
2411               case 2:
2412                 *q++ = curr;
2413                 *q++ = curr;
2414                 break;
2415               default:
2416                 sprintf(q, "%d", runlength);
2417                 while (*q) q++;
2418                 *q++ = curr;
2419                 break;
2420             }
2421             runlength = 1;
2422             curr = *p;
2423         }
2424     } while (*p++);
2425     *q = NULLCHAR;
2426 }
2427
2428 /* Telnet protocol requests from the front end */
2429 void
2430 TelnetRequest (unsigned char ddww, unsigned char option)
2431 {
2432     unsigned char msg[3];
2433     int outCount, outError;
2434
2435     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2436
2437     if (appData.debugMode) {
2438         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2439         switch (ddww) {
2440           case TN_DO:
2441             ddwwStr = "DO";
2442             break;
2443           case TN_DONT:
2444             ddwwStr = "DONT";
2445             break;
2446           case TN_WILL:
2447             ddwwStr = "WILL";
2448             break;
2449           case TN_WONT:
2450             ddwwStr = "WONT";
2451             break;
2452           default:
2453             ddwwStr = buf1;
2454             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2455             break;
2456         }
2457         switch (option) {
2458           case TN_ECHO:
2459             optionStr = "ECHO";
2460             break;
2461           default:
2462             optionStr = buf2;
2463             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2464             break;
2465         }
2466         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2467     }
2468     msg[0] = TN_IAC;
2469     msg[1] = ddww;
2470     msg[2] = option;
2471     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2472     if (outCount < 3) {
2473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2474     }
2475 }
2476
2477 void
2478 DoEcho ()
2479 {
2480     if (!appData.icsActive) return;
2481     TelnetRequest(TN_DO, TN_ECHO);
2482 }
2483
2484 void
2485 DontEcho ()
2486 {
2487     if (!appData.icsActive) return;
2488     TelnetRequest(TN_DONT, TN_ECHO);
2489 }
2490
2491 void
2492 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2493 {
2494     /* put the holdings sent to us by the server on the board holdings area */
2495     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2496     char p;
2497     ChessSquare piece;
2498
2499     if(gameInfo.holdingsWidth < 2)  return;
2500     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2501         return; // prevent overwriting by pre-board holdings
2502
2503     if( (int)lowestPiece >= BlackPawn ) {
2504         holdingsColumn = 0;
2505         countsColumn = 1;
2506         holdingsStartRow = handSize-1;
2507         direction = -1;
2508     } else {
2509         holdingsColumn = BOARD_WIDTH-1;
2510         countsColumn = BOARD_WIDTH-2;
2511         holdingsStartRow = 0;
2512         direction = 1;
2513     }
2514
2515     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2516         board[i][holdingsColumn] = EmptySquare;
2517         board[i][countsColumn]   = (ChessSquare) 0;
2518     }
2519     while( (p=*holdings++) != NULLCHAR ) {
2520         piece = CharToPiece( ToUpper(p) );
2521         if(piece == EmptySquare) continue;
2522         /*j = (int) piece - (int) WhitePawn;*/
2523         j = PieceToNumber(piece);
2524         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2525         if(j < 0) continue;               /* should not happen */
2526         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2527         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2528         board[holdingsStartRow+j*direction][countsColumn]++;
2529     }
2530 }
2531
2532
2533 void
2534 VariantSwitch (Board board, VariantClass newVariant)
2535 {
2536    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2537    static Board oldBoard;
2538
2539    startedFromPositionFile = FALSE;
2540    if(gameInfo.variant == newVariant) return;
2541
2542    /* [HGM] This routine is called each time an assignment is made to
2543     * gameInfo.variant during a game, to make sure the board sizes
2544     * are set to match the new variant. If that means adding or deleting
2545     * holdings, we shift the playing board accordingly
2546     * This kludge is needed because in ICS observe mode, we get boards
2547     * of an ongoing game without knowing the variant, and learn about the
2548     * latter only later. This can be because of the move list we requested,
2549     * in which case the game history is refilled from the beginning anyway,
2550     * but also when receiving holdings of a crazyhouse game. In the latter
2551     * case we want to add those holdings to the already received position.
2552     */
2553
2554
2555    if (appData.debugMode) {
2556      fprintf(debugFP, "Switch board from %s to %s\n",
2557              VariantName(gameInfo.variant), VariantName(newVariant));
2558      setbuf(debugFP, NULL);
2559    }
2560    shuffleOpenings = 0;       /* [HGM] shuffle */
2561    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2562    switch(newVariant)
2563      {
2564      case VariantShogi:
2565        newWidth = 9;  newHeight = 9;
2566        gameInfo.holdingsSize = 7;
2567      case VariantBughouse:
2568      case VariantCrazyhouse:
2569        newHoldingsWidth = 2; break;
2570      case VariantGreat:
2571        newWidth = 10;
2572      case VariantSuper:
2573        newHoldingsWidth = 2;
2574        gameInfo.holdingsSize = 8;
2575        break;
2576      case VariantGothic:
2577      case VariantCapablanca:
2578      case VariantCapaRandom:
2579        newWidth = 10;
2580      default:
2581        newHoldingsWidth = gameInfo.holdingsSize = 0;
2582      };
2583
2584    if(newWidth  != gameInfo.boardWidth  ||
2585       newHeight != gameInfo.boardHeight ||
2586       newHoldingsWidth != gameInfo.holdingsWidth ) {
2587
2588      /* shift position to new playing area, if needed */
2589      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2590        for(i=0; i<BOARD_HEIGHT; i++)
2591          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2592            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2593              board[i][j];
2594        for(i=0; i<newHeight; i++) {
2595          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2596          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2597        }
2598      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2599        for(i=0; i<BOARD_HEIGHT; i++)
2600          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2601            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2602              board[i][j];
2603      }
2604      board[HOLDINGS_SET] = 0;
2605      gameInfo.boardWidth  = newWidth;
2606      gameInfo.boardHeight = newHeight;
2607      gameInfo.holdingsWidth = newHoldingsWidth;
2608      gameInfo.variant = newVariant;
2609      InitDrawingSizes(-2, 0);
2610    } else gameInfo.variant = newVariant;
2611    CopyBoard(oldBoard, board);   // remember correctly formatted board
2612      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2613    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2614 }
2615
2616 static int loggedOn = FALSE;
2617
2618 /*-- Game start info cache: --*/
2619 int gs_gamenum;
2620 char gs_kind[MSG_SIZ];
2621 static char player1Name[128] = "";
2622 static char player2Name[128] = "";
2623 static char cont_seq[] = "\n\\   ";
2624 static int player1Rating = -1;
2625 static int player2Rating = -1;
2626 /*----------------------------*/
2627
2628 ColorClass curColor = ColorNormal;
2629 int suppressKibitz = 0;
2630
2631 // [HGM] seekgraph
2632 Boolean soughtPending = FALSE;
2633 Boolean seekGraphUp;
2634 #define MAX_SEEK_ADS 200
2635 #define SQUARE 0x80
2636 char *seekAdList[MAX_SEEK_ADS];
2637 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2638 float tcList[MAX_SEEK_ADS];
2639 char colorList[MAX_SEEK_ADS];
2640 int nrOfSeekAds = 0;
2641 int minRating = 1010, maxRating = 2800;
2642 int hMargin = 10, vMargin = 20, h, w;
2643 extern int squareSize, lineGap;
2644
2645 void
2646 PlotSeekAd (int i)
2647 {
2648         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2649         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2650         if(r < minRating+100 && r >=0 ) r = minRating+100;
2651         if(r > maxRating) r = maxRating;
2652         if(tc < 1.f) tc = 1.f;
2653         if(tc > 95.f) tc = 95.f;
2654         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2655         y = ((double)r - minRating)/(maxRating - minRating)
2656             * (h-vMargin-squareSize/8-1) + vMargin;
2657         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2658         if(strstr(seekAdList[i], " u ")) color = 1;
2659         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2660            !strstr(seekAdList[i], "bullet") &&
2661            !strstr(seekAdList[i], "blitz") &&
2662            !strstr(seekAdList[i], "standard") ) color = 2;
2663         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2664         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2665 }
2666
2667 void
2668 PlotSingleSeekAd (int i)
2669 {
2670         PlotSeekAd(i);
2671 }
2672
2673 void
2674 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2675 {
2676         char buf[MSG_SIZ], *ext = "";
2677         VariantClass v = StringToVariant(type);
2678         if(strstr(type, "wild")) {
2679             ext = type + 4; // append wild number
2680             if(v == VariantFischeRandom) type = "chess960"; else
2681             if(v == VariantLoadable) type = "setup"; else
2682             type = VariantName(v);
2683         }
2684         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2685         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2686             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2687             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2688             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2689             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2690             seekNrList[nrOfSeekAds] = nr;
2691             zList[nrOfSeekAds] = 0;
2692             seekAdList[nrOfSeekAds++] = StrSave(buf);
2693             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2694         }
2695 }
2696
2697 void
2698 EraseSeekDot (int i)
2699 {
2700     int x = xList[i], y = yList[i], d=squareSize/4, k;
2701     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2702     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2703     // now replot every dot that overlapped
2704     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2705         int xx = xList[k], yy = yList[k];
2706         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2707             DrawSeekDot(xx, yy, colorList[k]);
2708     }
2709 }
2710
2711 void
2712 RemoveSeekAd (int nr)
2713 {
2714         int i;
2715         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2716             EraseSeekDot(i);
2717             if(seekAdList[i]) free(seekAdList[i]);
2718             seekAdList[i] = seekAdList[--nrOfSeekAds];
2719             seekNrList[i] = seekNrList[nrOfSeekAds];
2720             ratingList[i] = ratingList[nrOfSeekAds];
2721             colorList[i]  = colorList[nrOfSeekAds];
2722             tcList[i] = tcList[nrOfSeekAds];
2723             xList[i]  = xList[nrOfSeekAds];
2724             yList[i]  = yList[nrOfSeekAds];
2725             zList[i]  = zList[nrOfSeekAds];
2726             seekAdList[nrOfSeekAds] = NULL;
2727             break;
2728         }
2729 }
2730
2731 Boolean
2732 MatchSoughtLine (char *line)
2733 {
2734     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2735     int nr, base, inc, u=0; char dummy;
2736
2737     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2738        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2739        (u=1) &&
2740        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2741         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2742         // match: compact and save the line
2743         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2744         return TRUE;
2745     }
2746     return FALSE;
2747 }
2748
2749 int
2750 DrawSeekGraph ()
2751 {
2752     int i;
2753     if(!seekGraphUp) return FALSE;
2754     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2755     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2756
2757     DrawSeekBackground(0, 0, w, h);
2758     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2759     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2760     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2761         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2762         yy = h-1-yy;
2763         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2764         if(i%500 == 0) {
2765             char buf[MSG_SIZ];
2766             snprintf(buf, MSG_SIZ, "%d", i);
2767             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2768         }
2769     }
2770     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2771     for(i=1; i<100; i+=(i<10?1:5)) {
2772         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2773         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2774         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2775             char buf[MSG_SIZ];
2776             snprintf(buf, MSG_SIZ, "%d", i);
2777             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2778         }
2779     }
2780     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2781     return TRUE;
2782 }
2783
2784 int
2785 SeekGraphClick (ClickType click, int x, int y, int moving)
2786 {
2787     static int lastDown = 0, displayed = 0, lastSecond;
2788     if(y < 0) return FALSE;
2789     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2790         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2791         if(!seekGraphUp) return FALSE;
2792         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2793         DrawPosition(TRUE, NULL);
2794         return TRUE;
2795     }
2796     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2797         if(click == Release || moving) return FALSE;
2798         nrOfSeekAds = 0;
2799         soughtPending = TRUE;
2800         SendToICS(ics_prefix);
2801         SendToICS("sought\n"); // should this be "sought all"?
2802     } else { // issue challenge based on clicked ad
2803         int dist = 10000; int i, closest = 0, second = 0;
2804         for(i=0; i<nrOfSeekAds; i++) {
2805             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2806             if(d < dist) { dist = d; closest = i; }
2807             second += (d - zList[i] < 120); // count in-range ads
2808             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2809         }
2810         if(dist < 120) {
2811             char buf[MSG_SIZ];
2812             second = (second > 1);
2813             if(displayed != closest || second != lastSecond) {
2814                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2815                 lastSecond = second; displayed = closest;
2816             }
2817             if(click == Press) {
2818                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2819                 lastDown = closest;
2820                 return TRUE;
2821             } // on press 'hit', only show info
2822             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2823             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2824             SendToICS(ics_prefix);
2825             SendToICS(buf);
2826             return TRUE; // let incoming board of started game pop down the graph
2827         } else if(click == Release) { // release 'miss' is ignored
2828             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2829             if(moving == 2) { // right up-click
2830                 nrOfSeekAds = 0; // refresh graph
2831                 soughtPending = TRUE;
2832                 SendToICS(ics_prefix);
2833                 SendToICS("sought\n"); // should this be "sought all"?
2834             }
2835             return TRUE;
2836         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2837         // press miss or release hit 'pop down' seek graph
2838         seekGraphUp = FALSE;
2839         DrawPosition(TRUE, NULL);
2840     }
2841     return TRUE;
2842 }
2843
2844 void
2845 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2846 {
2847 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2848 #define STARTED_NONE 0
2849 #define STARTED_MOVES 1
2850 #define STARTED_BOARD 2
2851 #define STARTED_OBSERVE 3
2852 #define STARTED_HOLDINGS 4
2853 #define STARTED_CHATTER 5
2854 #define STARTED_COMMENT 6
2855 #define STARTED_MOVES_NOHIDE 7
2856
2857     static int started = STARTED_NONE;
2858     static char parse[20000];
2859     static int parse_pos = 0;
2860     static char buf[BUF_SIZE + 1];
2861     static int firstTime = TRUE, intfSet = FALSE;
2862     static ColorClass prevColor = ColorNormal;
2863     static int savingComment = FALSE;
2864     static int cmatch = 0; // continuation sequence match
2865     char *bp;
2866     char str[MSG_SIZ];
2867     int i, oldi;
2868     int buf_len;
2869     int next_out;
2870     int tkind;
2871     int backup;    /* [DM] For zippy color lines */
2872     char *p;
2873     char talker[MSG_SIZ]; // [HGM] chat
2874     int channel, collective=0;
2875
2876     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2877
2878     if (appData.debugMode) {
2879       if (!error) {
2880         fprintf(debugFP, "<ICS: ");
2881         show_bytes(debugFP, data, count);
2882         fprintf(debugFP, "\n");
2883       }
2884     }
2885
2886     if (appData.debugMode) { int f = forwardMostMove;
2887         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2888                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2889                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2890     }
2891     if (count > 0) {
2892         /* If last read ended with a partial line that we couldn't parse,
2893            prepend it to the new read and try again. */
2894         if (leftover_len > 0) {
2895             for (i=0; i<leftover_len; i++)
2896               buf[i] = buf[leftover_start + i];
2897         }
2898
2899     /* copy new characters into the buffer */
2900     bp = buf + leftover_len;
2901     buf_len=leftover_len;
2902     for (i=0; i<count; i++)
2903     {
2904         // ignore these
2905         if (data[i] == '\r')
2906             continue;
2907
2908         // join lines split by ICS?
2909         if (!appData.noJoin)
2910         {
2911             /*
2912                 Joining just consists of finding matches against the
2913                 continuation sequence, and discarding that sequence
2914                 if found instead of copying it.  So, until a match
2915                 fails, there's nothing to do since it might be the
2916                 complete sequence, and thus, something we don't want
2917                 copied.
2918             */
2919             if (data[i] == cont_seq[cmatch])
2920             {
2921                 cmatch++;
2922                 if (cmatch == strlen(cont_seq))
2923                 {
2924                     cmatch = 0; // complete match.  just reset the counter
2925
2926                     /*
2927                         it's possible for the ICS to not include the space
2928                         at the end of the last word, making our [correct]
2929                         join operation fuse two separate words.  the server
2930                         does this when the space occurs at the width setting.
2931                     */
2932                     if (!buf_len || buf[buf_len-1] != ' ')
2933                     {
2934                         *bp++ = ' ';
2935                         buf_len++;
2936                     }
2937                 }
2938                 continue;
2939             }
2940             else if (cmatch)
2941             {
2942                 /*
2943                     match failed, so we have to copy what matched before
2944                     falling through and copying this character.  In reality,
2945                     this will only ever be just the newline character, but
2946                     it doesn't hurt to be precise.
2947                 */
2948                 strncpy(bp, cont_seq, cmatch);
2949                 bp += cmatch;
2950                 buf_len += cmatch;
2951                 cmatch = 0;
2952             }
2953         }
2954
2955         // copy this char
2956         *bp++ = data[i];
2957         buf_len++;
2958     }
2959
2960         buf[buf_len] = NULLCHAR;
2961 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2962         next_out = 0;
2963         leftover_start = 0;
2964
2965         i = 0;
2966         while (i < buf_len) {
2967             /* Deal with part of the TELNET option negotiation
2968                protocol.  We refuse to do anything beyond the
2969                defaults, except that we allow the WILL ECHO option,
2970                which ICS uses to turn off password echoing when we are
2971                directly connected to it.  We reject this option
2972                if localLineEditing mode is on (always on in xboard)
2973                and we are talking to port 23, which might be a real
2974                telnet server that will try to keep WILL ECHO on permanently.
2975              */
2976             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2977                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2978                 unsigned char option;
2979                 oldi = i;
2980                 switch ((unsigned char) buf[++i]) {
2981                   case TN_WILL:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WILL ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (remoteEchoOption) break;
2991                         if (appData.localLineEditing &&
2992                             atoi(appData.icsPort) == TN_PORT) {
2993                             TelnetRequest(TN_DONT, TN_ECHO);
2994                         } else {
2995                             EchoOff();
2996                             TelnetRequest(TN_DO, TN_ECHO);
2997                             remoteEchoOption = TRUE;
2998                         }
2999                         break;
3000                       default:
3001                         if (appData.debugMode)
3002                           fprintf(debugFP, "%d ", option);
3003                         /* Whatever this is, we don't want it. */
3004                         TelnetRequest(TN_DONT, option);
3005                         break;
3006                     }
3007                     break;
3008                   case TN_WONT:
3009                     if (appData.debugMode)
3010                       fprintf(debugFP, "\n<WONT ");
3011                     switch (option = (unsigned char) buf[++i]) {
3012                       case TN_ECHO:
3013                         if (appData.debugMode)
3014                           fprintf(debugFP, "ECHO ");
3015                         /* Reply only if this is a change, according
3016                            to the protocol rules. */
3017                         if (!remoteEchoOption) break;
3018                         EchoOn();
3019                         TelnetRequest(TN_DONT, TN_ECHO);
3020                         remoteEchoOption = FALSE;
3021                         break;
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", (unsigned char) option);
3025                         /* Whatever this is, it must already be turned
3026                            off, because we never agree to turn on
3027                            anything non-default, so according to the
3028                            protocol rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_DO:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<DO ");
3035                     switch (option = (unsigned char) buf[++i]) {
3036                       default:
3037                         /* Whatever this is, we refuse to do it. */
3038                         if (appData.debugMode)
3039                           fprintf(debugFP, "%d ", option);
3040                         TelnetRequest(TN_WONT, option);
3041                         break;
3042                     }
3043                     break;
3044                   case TN_DONT:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<DONT ");
3047                     switch (option = (unsigned char) buf[++i]) {
3048                       default:
3049                         if (appData.debugMode)
3050                           fprintf(debugFP, "%d ", option);
3051                         /* Whatever this is, we are already not doing
3052                            it, because we never agree to do anything
3053                            non-default, so according to the protocol
3054                            rules, we don't reply. */
3055                         break;
3056                     }
3057                     break;
3058                   case TN_IAC:
3059                     if (appData.debugMode)
3060                       fprintf(debugFP, "\n<IAC ");
3061                     /* Doubled IAC; pass it through */
3062                     i--;
3063                     break;
3064                   default:
3065                     if (appData.debugMode)
3066                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3067                     /* Drop all other telnet commands on the floor */
3068                     break;
3069                 }
3070                 if (oldi > next_out)
3071                   SendToPlayer(&buf[next_out], oldi - next_out);
3072                 if (++i > next_out)
3073                   next_out = i;
3074                 continue;
3075             }
3076
3077             /* OK, this at least will *usually* work */
3078             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3079                 loggedOn = TRUE;
3080             }
3081
3082             if (loggedOn && !intfSet) {
3083                 if (ics_type == ICS_ICC) {
3084                   snprintf(str, MSG_SIZ,
3085                           "/set-quietly interface %s\n/set-quietly style 12\n",
3086                           programVersion);
3087                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3088                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3089                 } else if (ics_type == ICS_CHESSNET) {
3090                   snprintf(str, MSG_SIZ, "/style 12\n");
3091                 } else {
3092                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3093                   strcat(str, programVersion);
3094                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3095                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3096                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3097 #ifdef WIN32
3098                   strcat(str, "$iset nohighlight 1\n");
3099 #endif
3100                   strcat(str, "$iset lock 1\n$style 12\n");
3101                 }
3102                 SendToICS(str);
3103                 NotifyFrontendLogin();
3104                 intfSet = TRUE;
3105             }
3106
3107             if (started == STARTED_COMMENT) {
3108                 /* Accumulate characters in comment */
3109                 parse[parse_pos++] = buf[i];
3110                 if (buf[i] == '\n') {
3111                     parse[parse_pos] = NULLCHAR;
3112                     if(chattingPartner>=0) {
3113                         char mess[MSG_SIZ];
3114                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3115                         OutputChatMessage(chattingPartner, mess);
3116                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3117                             int p;
3118                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3119                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3120                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3121                                 OutputChatMessage(p, mess);
3122                                 break;
3123                             }
3124                         }
3125                         chattingPartner = -1;
3126                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3127                         collective = 0;
3128                     } else
3129                     if(!suppressKibitz) // [HGM] kibitz
3130                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3131                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3132                         int nrDigit = 0, nrAlph = 0, j;
3133                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3134                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3135                         parse[parse_pos] = NULLCHAR;
3136                         // try to be smart: if it does not look like search info, it should go to
3137                         // ICS interaction window after all, not to engine-output window.
3138                         for(j=0; j<parse_pos; j++) { // count letters and digits
3139                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3140                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3141                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3142                         }
3143                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3144                             int depth=0; float score;
3145                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3146                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3147                                 pvInfoList[forwardMostMove-1].depth = depth;
3148                                 pvInfoList[forwardMostMove-1].score = 100*score;
3149                             }
3150                             OutputKibitz(suppressKibitz, parse);
3151                         } else {
3152                             char tmp[MSG_SIZ];
3153                             if(gameMode == IcsObserving) // restore original ICS messages
3154                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3155                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3156                             else
3157                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3158                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3159                             SendToPlayer(tmp, strlen(tmp));
3160                         }
3161                         next_out = i+1; // [HGM] suppress printing in ICS window
3162                     }
3163                     started = STARTED_NONE;
3164                 } else {
3165                     /* Don't match patterns against characters in comment */
3166                     i++;
3167                     continue;
3168                 }
3169             }
3170             if (started == STARTED_CHATTER) {
3171                 if (buf[i] != '\n') {
3172                     /* Don't match patterns against characters in chatter */
3173                     i++;
3174                     continue;
3175                 }
3176                 started = STARTED_NONE;
3177                 if(suppressKibitz) next_out = i+1;
3178             }
3179
3180             /* Kludge to deal with rcmd protocol */
3181             if (firstTime && looking_at(buf, &i, "\001*")) {
3182                 DisplayFatalError(&buf[1], 0, 1);
3183                 continue;
3184             } else {
3185                 firstTime = FALSE;
3186             }
3187
3188             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3189                 ics_type = ICS_ICC;
3190                 ics_prefix = "/";
3191                 if (appData.debugMode)
3192                   fprintf(debugFP, "ics_type %d\n", ics_type);
3193                 continue;
3194             }
3195             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3196                 ics_type = ICS_FICS;
3197                 ics_prefix = "$";
3198                 if (appData.debugMode)
3199                   fprintf(debugFP, "ics_type %d\n", ics_type);
3200                 continue;
3201             }
3202             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3203                 ics_type = ICS_CHESSNET;
3204                 ics_prefix = "/";
3205                 if (appData.debugMode)
3206                   fprintf(debugFP, "ics_type %d\n", ics_type);
3207                 continue;
3208             }
3209
3210             if (!loggedOn &&
3211                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3212                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3213                  looking_at(buf, &i, "will be \"*\""))) {
3214               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3215               continue;
3216             }
3217
3218             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3219               char buf[MSG_SIZ];
3220               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3221               DisplayIcsInteractionTitle(buf);
3222               have_set_title = TRUE;
3223             }
3224
3225             /* skip finger notes */
3226             if (started == STARTED_NONE &&
3227                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3228                  (buf[i] == '1' && buf[i+1] == '0')) &&
3229                 buf[i+2] == ':' && buf[i+3] == ' ') {
3230               started = STARTED_CHATTER;
3231               i += 3;
3232               continue;
3233             }
3234
3235             oldi = i;
3236             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3237             if(appData.seekGraph) {
3238                 if(soughtPending && MatchSoughtLine(buf+i)) {
3239                     i = strstr(buf+i, "rated") - buf;
3240                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241                     next_out = leftover_start = i;
3242                     started = STARTED_CHATTER;
3243                     suppressKibitz = TRUE;
3244                     continue;
3245                 }
3246                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3247                         && looking_at(buf, &i, "* ads displayed")) {
3248                     soughtPending = FALSE;
3249                     seekGraphUp = TRUE;
3250                     DrawSeekGraph();
3251                     continue;
3252                 }
3253                 if(appData.autoRefresh) {
3254                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3255                         int s = (ics_type == ICS_ICC); // ICC format differs
3256                         if(seekGraphUp)
3257                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3258                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3259                         looking_at(buf, &i, "*% "); // eat prompt
3260                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3261                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = i; // suppress
3263                         continue;
3264                     }
3265                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3266                         char *p = star_match[0];
3267                         while(*p) {
3268                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3269                             while(*p && *p++ != ' '); // next
3270                         }
3271                         looking_at(buf, &i, "*% "); // eat prompt
3272                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3273                         next_out = i;
3274                         continue;
3275                     }
3276                 }
3277             }
3278
3279             /* skip formula vars */
3280             if (started == STARTED_NONE &&
3281                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3282               started = STARTED_CHATTER;
3283               i += 3;
3284               continue;
3285             }
3286
3287             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3288             if (appData.autoKibitz && started == STARTED_NONE &&
3289                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3290                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3291                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3292                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3293                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3294                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3295                         suppressKibitz = TRUE;
3296                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297                         next_out = i;
3298                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3299                                 && (gameMode == IcsPlayingWhite)) ||
3300                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3301                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3302                             started = STARTED_CHATTER; // own kibitz we simply discard
3303                         else {
3304                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3305                             parse_pos = 0; parse[0] = NULLCHAR;
3306                             savingComment = TRUE;
3307                             suppressKibitz = gameMode != IcsObserving ? 2 :
3308                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3309                         }
3310                         continue;
3311                 } else
3312                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3313                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3314                          && atoi(star_match[0])) {
3315                     // suppress the acknowledgements of our own autoKibitz
3316                     char *p;
3317                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3318                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3319                     SendToPlayer(star_match[0], strlen(star_match[0]));
3320                     if(looking_at(buf, &i, "*% ")) // eat prompt
3321                         suppressKibitz = FALSE;
3322                     next_out = i;
3323                     continue;
3324                 }
3325             } // [HGM] kibitz: end of patch
3326
3327             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3328
3329             // [HGM] chat: intercept tells by users for which we have an open chat window
3330             channel = -1;
3331             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3332                                            looking_at(buf, &i, "* whispers:") ||
3333                                            looking_at(buf, &i, "* kibitzes:") ||
3334                                            looking_at(buf, &i, "* shouts:") ||
3335                                            looking_at(buf, &i, "* c-shouts:") ||
3336                                            looking_at(buf, &i, "--> * ") ||
3337                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3338                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3339                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3340                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3341                 int p;
3342                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3343                 chattingPartner = -1; collective = 0;
3344
3345                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3346                 for(p=0; p<MAX_CHAT; p++) {
3347                     collective = 1;
3348                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3349                     talker[0] = '['; strcat(talker, "] ");
3350                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3351                     chattingPartner = p; break;
3352                     }
3353                 } else
3354                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3355                 for(p=0; p<MAX_CHAT; p++) {
3356                     collective = 1;
3357                     if(!strcmp("kibitzes", chatPartner[p])) {
3358                         talker[0] = '['; strcat(talker, "] ");
3359                         chattingPartner = p; break;
3360                     }
3361                 } else
3362                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3363                 for(p=0; p<MAX_CHAT; p++) {
3364                     collective = 1;
3365                     if(!strcmp("whispers", chatPartner[p])) {
3366                         talker[0] = '['; strcat(talker, "] ");
3367                         chattingPartner = p; break;
3368                     }
3369                 } else
3370                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3371                   if(buf[i-8] == '-' && buf[i-3] == 't')
3372                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3373                     collective = 1;
3374                     if(!strcmp("c-shouts", chatPartner[p])) {
3375                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3376                         chattingPartner = p; break;
3377                     }
3378                   }
3379                   if(chattingPartner < 0)
3380                   for(p=0; p<MAX_CHAT; p++) {
3381                     collective = 1;
3382                     if(!strcmp("shouts", chatPartner[p])) {
3383                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3384                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3385                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3386                         chattingPartner = p; break;
3387                     }
3388                   }
3389                 }
3390                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3391                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3392                     talker[0] = 0;
3393                     Colorize(ColorTell, FALSE);
3394                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3395                     collective |= 2;
3396                     chattingPartner = p; break;
3397                 }
3398                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3399                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3400                     started = STARTED_COMMENT;
3401                     parse_pos = 0; parse[0] = NULLCHAR;
3402                     savingComment = 3 + chattingPartner; // counts as TRUE
3403                     if(collective == 3) i = oldi; else {
3404                         suppressKibitz = TRUE;
3405                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3406                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3407                         continue;
3408                     }
3409                 }
3410             } // [HGM] chat: end of patch
3411
3412           backup = i;
3413             if (appData.zippyTalk || appData.zippyPlay) {
3414                 /* [DM] Backup address for color zippy lines */
3415 #if ZIPPY
3416                if (loggedOn == TRUE)
3417                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3418                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3419                        ;
3420 #endif
3421             } // [DM] 'else { ' deleted
3422                 if (
3423                     /* Regular tells and says */
3424                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3425                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3426                     looking_at(buf, &i, "* says: ") ||
3427                     /* Don't color "message" or "messages" output */
3428                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3429                     looking_at(buf, &i, "*. * at *:*: ") ||
3430                     looking_at(buf, &i, "--* (*:*): ") ||
3431                     /* Message notifications (same color as tells) */
3432                     looking_at(buf, &i, "* has left a message ") ||
3433                     looking_at(buf, &i, "* just sent you a message:\n") ||
3434                     /* Whispers and kibitzes */
3435                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3436                     looking_at(buf, &i, "* kibitzes: ") ||
3437                     /* Channel tells */
3438                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3439
3440                   if (tkind == 1 && strchr(star_match[0], ':')) {
3441                       /* Avoid "tells you:" spoofs in channels */
3442                      tkind = 3;
3443                   }
3444                   if (star_match[0][0] == NULLCHAR ||
3445                       strchr(star_match[0], ' ') ||
3446                       (tkind == 3 && strchr(star_match[1], ' '))) {
3447                     /* Reject bogus matches */
3448                     i = oldi;
3449                   } else {
3450                     if (appData.colorize) {
3451                       if (oldi > next_out) {
3452                         SendToPlayer(&buf[next_out], oldi - next_out);
3453                         next_out = oldi;
3454                       }
3455                       switch (tkind) {
3456                       case 1:
3457                         Colorize(ColorTell, FALSE);
3458                         curColor = ColorTell;
3459                         break;
3460                       case 2:
3461                         Colorize(ColorKibitz, FALSE);
3462                         curColor = ColorKibitz;
3463                         break;
3464                       case 3:
3465                         p = strrchr(star_match[1], '(');
3466                         if (p == NULL) {
3467                           p = star_match[1];
3468                         } else {
3469                           p++;
3470                         }
3471                         if (atoi(p) == 1) {
3472                           Colorize(ColorChannel1, FALSE);
3473                           curColor = ColorChannel1;
3474                         } else {
3475                           Colorize(ColorChannel, FALSE);
3476                           curColor = ColorChannel;
3477                         }
3478                         break;
3479                       case 5:
3480                         curColor = ColorNormal;
3481                         break;
3482                       }
3483                     }
3484                     if (started == STARTED_NONE && appData.autoComment &&
3485                         (gameMode == IcsObserving ||
3486                          gameMode == IcsPlayingWhite ||
3487                          gameMode == IcsPlayingBlack)) {
3488                       parse_pos = i - oldi;
3489                       memcpy(parse, &buf[oldi], parse_pos);
3490                       parse[parse_pos] = NULLCHAR;
3491                       started = STARTED_COMMENT;
3492                       savingComment = TRUE;
3493                     } else if(collective != 3) {
3494                       started = STARTED_CHATTER;
3495                       savingComment = FALSE;
3496                     }
3497                     loggedOn = TRUE;
3498                     continue;
3499                   }
3500                 }
3501
3502                 if (looking_at(buf, &i, "* s-shouts: ") ||
3503                     looking_at(buf, &i, "* c-shouts: ")) {
3504                     if (appData.colorize) {
3505                         if (oldi > next_out) {
3506                             SendToPlayer(&buf[next_out], oldi - next_out);
3507                             next_out = oldi;
3508                         }
3509                         Colorize(ColorSShout, FALSE);
3510                         curColor = ColorSShout;
3511                     }
3512                     loggedOn = TRUE;
3513                     started = STARTED_CHATTER;
3514                     continue;
3515                 }
3516
3517                 if (looking_at(buf, &i, "--->")) {
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* shouts: ") ||
3523                     looking_at(buf, &i, "--> ")) {
3524                     if (appData.colorize) {
3525                         if (oldi > next_out) {
3526                             SendToPlayer(&buf[next_out], oldi - next_out);
3527                             next_out = oldi;
3528                         }
3529                         Colorize(ColorShout, FALSE);
3530                         curColor = ColorShout;
3531                     }
3532                     loggedOn = TRUE;
3533                     started = STARTED_CHATTER;
3534                     continue;
3535                 }
3536
3537                 if (looking_at( buf, &i, "Challenge:")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorChallenge, FALSE);
3544                         curColor = ColorChallenge;
3545                     }
3546                     loggedOn = TRUE;
3547                     continue;
3548                 }
3549
3550                 if (looking_at(buf, &i, "* offers you") ||
3551                     looking_at(buf, &i, "* offers to be") ||
3552                     looking_at(buf, &i, "* would like to") ||
3553                     looking_at(buf, &i, "* requests to") ||
3554                     looking_at(buf, &i, "Your opponent offers") ||
3555                     looking_at(buf, &i, "Your opponent requests")) {
3556
3557                     if (appData.colorize) {
3558                         if (oldi > next_out) {
3559                             SendToPlayer(&buf[next_out], oldi - next_out);
3560                             next_out = oldi;
3561                         }
3562                         Colorize(ColorRequest, FALSE);
3563                         curColor = ColorRequest;
3564                     }
3565                     continue;
3566                 }
3567
3568                 if (looking_at(buf, &i, "* (*) seeking")) {
3569                     if (appData.colorize) {
3570                         if (oldi > next_out) {
3571                             SendToPlayer(&buf[next_out], oldi - next_out);
3572                             next_out = oldi;
3573                         }
3574                         Colorize(ColorSeek, FALSE);
3575                         curColor = ColorSeek;
3576                     }
3577                     continue;
3578             }
3579
3580           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3581
3582             if (looking_at(buf, &i, "\\   ")) {
3583                 if (prevColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(prevColor, TRUE);
3589                     curColor = prevColor;
3590                 }
3591                 if (savingComment) {
3592                     parse_pos = i - oldi;
3593                     memcpy(parse, &buf[oldi], parse_pos);
3594                     parse[parse_pos] = NULLCHAR;
3595                     started = STARTED_COMMENT;
3596                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3597                         chattingPartner = savingComment - 3; // kludge to remember the box
3598                 } else {
3599                     started = STARTED_CHATTER;
3600                 }
3601                 continue;
3602             }
3603
3604             if (looking_at(buf, &i, "Black Strength :") ||
3605                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3606                 looking_at(buf, &i, "<10>") ||
3607                 looking_at(buf, &i, "#@#")) {
3608                 /* Wrong board style */
3609                 loggedOn = TRUE;
3610                 SendToICS(ics_prefix);
3611                 SendToICS("set style 12\n");
3612                 SendToICS(ics_prefix);
3613                 SendToICS("refresh\n");
3614                 continue;
3615             }
3616
3617             if (looking_at(buf, &i, "login:")) {
3618               if (!have_sent_ICS_logon) {
3619                 if(ICSInitScript())
3620                   have_sent_ICS_logon = 1;
3621                 else // no init script was found
3622                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3623               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3624                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3625               }
3626                 continue;
3627             }
3628
3629             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3630                 (looking_at(buf, &i, "\n<12> ") ||
3631                  looking_at(buf, &i, "<12> "))) {
3632                 loggedOn = TRUE;
3633                 if (oldi > next_out) {
3634                     SendToPlayer(&buf[next_out], oldi - next_out);
3635                 }
3636                 next_out = i;
3637                 started = STARTED_BOARD;
3638                 parse_pos = 0;
3639                 continue;
3640             }
3641
3642             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3643                 looking_at(buf, &i, "<b1> ")) {
3644                 if (oldi > next_out) {
3645                     SendToPlayer(&buf[next_out], oldi - next_out);
3646                 }
3647                 next_out = i;
3648                 started = STARTED_HOLDINGS;
3649                 parse_pos = 0;
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3654                 loggedOn = TRUE;
3655                 /* Header for a move list -- first line */
3656
3657                 switch (ics_getting_history) {
3658                   case H_FALSE:
3659                     switch (gameMode) {
3660                       case IcsIdle:
3661                       case BeginningOfGame:
3662                         /* User typed "moves" or "oldmoves" while we
3663                            were idle.  Pretend we asked for these
3664                            moves and soak them up so user can step
3665                            through them and/or save them.
3666                            */
3667                         Reset(FALSE, TRUE);
3668                         gameMode = IcsObserving;
3669                         ModeHighlight();
3670                         ics_gamenum = -1;
3671                         ics_getting_history = H_GOT_UNREQ_HEADER;
3672                         break;
3673                       case EditGame: /*?*/
3674                       case EditPosition: /*?*/
3675                         /* Should above feature work in these modes too? */
3676                         /* For now it doesn't */
3677                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3678                         break;
3679                       default:
3680                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3681                         break;
3682                     }
3683                     break;
3684                   case H_REQUESTED:
3685                     /* Is this the right one? */
3686                     if (gameInfo.white && gameInfo.black &&
3687                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3688                         strcmp(gameInfo.black, star_match[2]) == 0) {
3689                         /* All is well */
3690                         ics_getting_history = H_GOT_REQ_HEADER;
3691                     }
3692                     break;
3693                   case H_GOT_REQ_HEADER:
3694                   case H_GOT_UNREQ_HEADER:
3695                   case H_GOT_UNWANTED_HEADER:
3696                   case H_GETTING_MOVES:
3697                     /* Should not happen */
3698                     DisplayError(_("Error gathering move list: two headers"), 0);
3699                     ics_getting_history = H_FALSE;
3700                     break;
3701                 }
3702
3703                 /* Save player ratings into gameInfo if needed */
3704                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3705                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3706                     (gameInfo.whiteRating == -1 ||
3707                      gameInfo.blackRating == -1)) {
3708
3709                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3710                     gameInfo.blackRating = string_to_rating(star_match[3]);
3711                     if (appData.debugMode)
3712                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3713                               gameInfo.whiteRating, gameInfo.blackRating);
3714                 }
3715                 continue;
3716             }
3717
3718             if (looking_at(buf, &i,
3719               "* * match, initial time: * minute*, increment: * second")) {
3720                 /* Header for a move list -- second line */
3721                 /* Initial board will follow if this is a wild game */
3722                 if (gameInfo.event != NULL) free(gameInfo.event);
3723                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3724                 gameInfo.event = StrSave(str);
3725                 /* [HGM] we switched variant. Translate boards if needed. */
3726                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3727                 continue;
3728             }
3729
3730             if (looking_at(buf, &i, "Move  ")) {
3731                 /* Beginning of a move list */
3732                 switch (ics_getting_history) {
3733                   case H_FALSE:
3734                     /* Normally should not happen */
3735                     /* Maybe user hit reset while we were parsing */
3736                     break;
3737                   case H_REQUESTED:
3738                     /* Happens if we are ignoring a move list that is not
3739                      * the one we just requested.  Common if the user
3740                      * tries to observe two games without turning off
3741                      * getMoveList */
3742                     break;
3743                   case H_GETTING_MOVES:
3744                     /* Should not happen */
3745                     DisplayError(_("Error gathering move list: nested"), 0);
3746                     ics_getting_history = H_FALSE;
3747                     break;
3748                   case H_GOT_REQ_HEADER:
3749                     ics_getting_history = H_GETTING_MOVES;
3750                     started = STARTED_MOVES;
3751                     parse_pos = 0;
3752                     if (oldi > next_out) {
3753                         SendToPlayer(&buf[next_out], oldi - next_out);
3754                     }
3755                     break;
3756                   case H_GOT_UNREQ_HEADER:
3757                     ics_getting_history = H_GETTING_MOVES;
3758                     started = STARTED_MOVES_NOHIDE;
3759                     parse_pos = 0;
3760                     break;
3761                   case H_GOT_UNWANTED_HEADER:
3762                     ics_getting_history = H_FALSE;
3763                     break;
3764                 }
3765                 continue;
3766             }
3767
3768             if (looking_at(buf, &i, "% ") ||
3769                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3770                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3771                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3772                     soughtPending = FALSE;
3773                     seekGraphUp = TRUE;
3774                     DrawSeekGraph();
3775                 }
3776                 if(suppressKibitz) next_out = i;
3777                 savingComment = FALSE;
3778                 suppressKibitz = 0;
3779                 switch (started) {
3780                   case STARTED_MOVES:
3781                   case STARTED_MOVES_NOHIDE:
3782                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3783                     parse[parse_pos + i - oldi] = NULLCHAR;
3784                     ParseGameHistory(parse);
3785 #if ZIPPY
3786                     if (appData.zippyPlay && first.initDone) {
3787                         FeedMovesToProgram(&first, forwardMostMove);
3788                         if (gameMode == IcsPlayingWhite) {
3789                             if (WhiteOnMove(forwardMostMove)) {
3790                                 if (first.sendTime) {
3791                                   if (first.useColors) {
3792                                     SendToProgram("black\n", &first);
3793                                   }
3794                                   SendTimeRemaining(&first, TRUE);
3795                                 }
3796                                 if (first.useColors) {
3797                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3798                                 }
3799                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3800                                 first.maybeThinking = TRUE;
3801                             } else {
3802                                 if (first.usePlayother) {
3803                                   if (first.sendTime) {
3804                                     SendTimeRemaining(&first, TRUE);
3805                                   }
3806                                   SendToProgram("playother\n", &first);
3807                                   firstMove = FALSE;
3808                                 } else {
3809                                   firstMove = TRUE;
3810                                 }
3811                             }
3812                         } else if (gameMode == IcsPlayingBlack) {
3813                             if (!WhiteOnMove(forwardMostMove)) {
3814                                 if (first.sendTime) {
3815                                   if (first.useColors) {
3816                                     SendToProgram("white\n", &first);
3817                                   }
3818                                   SendTimeRemaining(&first, FALSE);
3819                                 }
3820                                 if (first.useColors) {
3821                                   SendToProgram("black\n", &first);
3822                                 }
3823                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3824                                 first.maybeThinking = TRUE;
3825                             } else {
3826                                 if (first.usePlayother) {
3827                                   if (first.sendTime) {
3828                                     SendTimeRemaining(&first, FALSE);
3829                                   }
3830                                   SendToProgram("playother\n", &first);
3831                                   firstMove = FALSE;
3832                                 } else {
3833                                   firstMove = TRUE;
3834                                 }
3835                             }
3836                         }
3837                     }
3838 #endif
3839                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3840                         /* Moves came from oldmoves or moves command
3841                            while we weren't doing anything else.
3842                            */
3843                         currentMove = forwardMostMove;
3844                         ClearHighlights();/*!!could figure this out*/
3845                         flipView = appData.flipView;
3846                         DrawPosition(TRUE, boards[currentMove]);
3847                         DisplayBothClocks();
3848                         snprintf(str, MSG_SIZ, "%s %s %s",
3849                                 gameInfo.white, _("vs."),  gameInfo.black);
3850                         DisplayTitle(str);
3851                         gameMode = IcsIdle;
3852                     } else {
3853                         /* Moves were history of an active game */
3854                         if (gameInfo.resultDetails != NULL) {
3855                             free(gameInfo.resultDetails);
3856                             gameInfo.resultDetails = NULL;
3857                         }
3858                     }
3859                     HistorySet(parseList, backwardMostMove,
3860                                forwardMostMove, currentMove-1);
3861                     DisplayMove(currentMove - 1);
3862                     if (started == STARTED_MOVES) next_out = i;
3863                     started = STARTED_NONE;
3864                     ics_getting_history = H_FALSE;
3865                     break;
3866
3867                   case STARTED_OBSERVE:
3868                     started = STARTED_NONE;
3869                     SendToICS(ics_prefix);
3870                     SendToICS("refresh\n");
3871                     break;
3872
3873                   default:
3874                     break;
3875                 }
3876                 if(bookHit) { // [HGM] book: simulate book reply
3877                     static char bookMove[MSG_SIZ]; // a bit generous?
3878
3879                     programStats.nodes = programStats.depth = programStats.time =
3880                     programStats.score = programStats.got_only_move = 0;
3881                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3882
3883                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3884                     strcat(bookMove, bookHit);
3885                     HandleMachineMove(bookMove, &first);
3886                 }
3887                 continue;
3888             }
3889
3890             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3891                  started == STARTED_HOLDINGS ||
3892                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3893                 /* Accumulate characters in move list or board */
3894                 parse[parse_pos++] = buf[i];
3895             }
3896
3897             /* Start of game messages.  Mostly we detect start of game
3898                when the first board image arrives.  On some versions
3899                of the ICS, though, we need to do a "refresh" after starting
3900                to observe in order to get the current board right away. */
3901             if (looking_at(buf, &i, "Adding game * to observation list")) {
3902                 started = STARTED_OBSERVE;
3903                 continue;
3904             }
3905
3906             /* Handle auto-observe */
3907             if (appData.autoObserve &&
3908                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3909                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3910                 char *player;
3911                 /* Choose the player that was highlighted, if any. */
3912                 if (star_match[0][0] == '\033' ||
3913                     star_match[1][0] != '\033') {
3914                     player = star_match[0];
3915                 } else {
3916                     player = star_match[2];
3917                 }
3918                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3919                         ics_prefix, StripHighlightAndTitle(player));
3920                 SendToICS(str);
3921
3922                 /* Save ratings from notify string */
3923                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3924                 player1Rating = string_to_rating(star_match[1]);
3925                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3926                 player2Rating = string_to_rating(star_match[3]);
3927
3928                 if (appData.debugMode)
3929                   fprintf(debugFP,
3930                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3931                           player1Name, player1Rating,
3932                           player2Name, player2Rating);
3933
3934                 continue;
3935             }
3936
3937             /* Deal with automatic examine mode after a game,
3938                and with IcsObserving -> IcsExamining transition */
3939             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3940                 looking_at(buf, &i, "has made you an examiner of game *")) {
3941
3942                 int gamenum = atoi(star_match[0]);
3943                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3944                     gamenum == ics_gamenum) {
3945                     /* We were already playing or observing this game;
3946                        no need to refetch history */
3947                     gameMode = IcsExamining;
3948                     if (pausing) {
3949                         pauseExamForwardMostMove = forwardMostMove;
3950                     } else if (currentMove < forwardMostMove) {
3951                         ForwardInner(forwardMostMove);
3952                     }
3953                 } else {
3954                     /* I don't think this case really can happen */
3955                     SendToICS(ics_prefix);
3956                     SendToICS("refresh\n");
3957                 }
3958                 continue;
3959             }
3960
3961             /* Error messages */
3962 //          if (ics_user_moved) {
3963             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3964                 if (looking_at(buf, &i, "Illegal move") ||
3965                     looking_at(buf, &i, "Not a legal move") ||
3966                     looking_at(buf, &i, "Your king is in check") ||
3967                     looking_at(buf, &i, "It isn't your turn") ||
3968                     looking_at(buf, &i, "It is not your move")) {
3969                     /* Illegal move */
3970                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3971                         currentMove = forwardMostMove-1;
3972                         DisplayMove(currentMove - 1); /* before DMError */
3973                         DrawPosition(FALSE, boards[currentMove]);
3974                         SwitchClocks(forwardMostMove-1); // [HGM] race
3975                         DisplayBothClocks();
3976                     }
3977                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3978                     ics_user_moved = 0;
3979                     continue;
3980                 }
3981             }
3982
3983             if (looking_at(buf, &i, "still have time") ||
3984                 looking_at(buf, &i, "not out of time") ||
3985                 looking_at(buf, &i, "either player is out of time") ||
3986                 looking_at(buf, &i, "has timeseal; checking")) {
3987                 /* We must have called his flag a little too soon */
3988                 whiteFlag = blackFlag = FALSE;
3989                 continue;
3990             }
3991
3992             if (looking_at(buf, &i, "added * seconds to") ||
3993                 looking_at(buf, &i, "seconds were added to")) {
3994                 /* Update the clocks */
3995                 SendToICS(ics_prefix);
3996                 SendToICS("refresh\n");
3997                 continue;
3998             }
3999
4000             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
4001                 ics_clock_paused = TRUE;
4002                 StopClocks();
4003                 continue;
4004             }
4005
4006             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
4007                 ics_clock_paused = FALSE;
4008                 StartClocks();
4009                 continue;
4010             }
4011
4012             /* Grab player ratings from the Creating: message.
4013                Note we have to check for the special case when
4014                the ICS inserts things like [white] or [black]. */
4015             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4016                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4017                 /* star_matches:
4018                    0    player 1 name (not necessarily white)
4019                    1    player 1 rating
4020                    2    empty, white, or black (IGNORED)
4021                    3    player 2 name (not necessarily black)
4022                    4    player 2 rating
4023
4024                    The names/ratings are sorted out when the game
4025                    actually starts (below).
4026                 */
4027                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4028                 player1Rating = string_to_rating(star_match[1]);
4029                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4030                 player2Rating = string_to_rating(star_match[4]);
4031
4032                 if (appData.debugMode)
4033                   fprintf(debugFP,
4034                           "Ratings from 'Creating:' %s %d, %s %d\n",
4035                           player1Name, player1Rating,
4036                           player2Name, player2Rating);
4037
4038                 continue;
4039             }
4040
4041             /* Improved generic start/end-of-game messages */
4042             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4043                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4044                 /* If tkind == 0: */
4045                 /* star_match[0] is the game number */
4046                 /*           [1] is the white player's name */
4047                 /*           [2] is the black player's name */
4048                 /* For end-of-game: */
4049                 /*           [3] is the reason for the game end */
4050                 /*           [4] is a PGN end game-token, preceded by " " */
4051                 /* For start-of-game: */
4052                 /*           [3] begins with "Creating" or "Continuing" */
4053                 /*           [4] is " *" or empty (don't care). */
4054                 int gamenum = atoi(star_match[0]);
4055                 char *whitename, *blackname, *why, *endtoken;
4056                 ChessMove endtype = EndOfFile;
4057
4058                 if (tkind == 0) {
4059                   whitename = star_match[1];
4060                   blackname = star_match[2];
4061                   why = star_match[3];
4062                   endtoken = star_match[4];
4063                 } else {
4064                   whitename = star_match[1];
4065                   blackname = star_match[3];
4066                   why = star_match[5];
4067                   endtoken = star_match[6];
4068                 }
4069
4070                 /* Game start messages */
4071                 if (strncmp(why, "Creating ", 9) == 0 ||
4072                     strncmp(why, "Continuing ", 11) == 0) {
4073                     gs_gamenum = gamenum;
4074                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4075                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4076                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4077 #if ZIPPY
4078                     if (appData.zippyPlay) {
4079                         ZippyGameStart(whitename, blackname);
4080                     }
4081 #endif /*ZIPPY*/
4082                     partnerBoardValid = FALSE; // [HGM] bughouse
4083                     continue;
4084                 }
4085
4086                 /* Game end messages */
4087                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4088                     ics_gamenum != gamenum) {
4089                     continue;
4090                 }
4091                 while (endtoken[0] == ' ') endtoken++;
4092                 switch (endtoken[0]) {
4093                   case '*':
4094                   default:
4095                     endtype = GameUnfinished;
4096                     break;
4097                   case '0':
4098                     endtype = BlackWins;
4099                     break;
4100                   case '1':
4101                     if (endtoken[1] == '/')
4102                       endtype = GameIsDrawn;
4103                     else
4104                       endtype = WhiteWins;
4105                     break;
4106                 }
4107                 GameEnds(endtype, why, GE_ICS);
4108 #if ZIPPY
4109                 if (appData.zippyPlay && first.initDone) {
4110                     ZippyGameEnd(endtype, why);
4111                     if (first.pr == NoProc) {
4112                       /* Start the next process early so that we'll
4113                          be ready for the next challenge */
4114                       StartChessProgram(&first);
4115                     }
4116                     /* Send "new" early, in case this command takes
4117                        a long time to finish, so that we'll be ready
4118                        for the next challenge. */
4119                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4120                     Reset(TRUE, TRUE);
4121                 }
4122 #endif /*ZIPPY*/
4123                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4124                 continue;
4125             }
4126
4127             if (looking_at(buf, &i, "Removing game * from observation") ||
4128                 looking_at(buf, &i, "no longer observing game *") ||
4129                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4130                 if (gameMode == IcsObserving &&
4131                     atoi(star_match[0]) == ics_gamenum)
4132                   {
4133                       /* icsEngineAnalyze */
4134                       if (appData.icsEngineAnalyze) {
4135                             ExitAnalyzeMode();
4136                             ModeHighlight();
4137                       }
4138                       StopClocks();
4139                       gameMode = IcsIdle;
4140                       ics_gamenum = -1;
4141                       ics_user_moved = FALSE;
4142                   }
4143                 continue;
4144             }
4145
4146             if (looking_at(buf, &i, "no longer examining game *")) {
4147                 if (gameMode == IcsExamining &&
4148                     atoi(star_match[0]) == ics_gamenum)
4149                   {
4150                       gameMode = IcsIdle;
4151                       ics_gamenum = -1;
4152                       ics_user_moved = FALSE;
4153                   }
4154                 continue;
4155             }
4156
4157             /* Advance leftover_start past any newlines we find,
4158                so only partial lines can get reparsed */
4159             if (looking_at(buf, &i, "\n")) {
4160                 prevColor = curColor;
4161                 if (curColor != ColorNormal) {
4162                     if (oldi > next_out) {
4163                         SendToPlayer(&buf[next_out], oldi - next_out);
4164                         next_out = oldi;
4165                     }
4166                     Colorize(ColorNormal, FALSE);
4167                     curColor = ColorNormal;
4168                 }
4169                 if (started == STARTED_BOARD) {
4170                     started = STARTED_NONE;
4171                     parse[parse_pos] = NULLCHAR;
4172                     ParseBoard12(parse);
4173                     ics_user_moved = 0;
4174
4175                     /* Send premove here */
4176                     if (appData.premove) {
4177                       char str[MSG_SIZ];
4178                       if (currentMove == 0 &&
4179                           gameMode == IcsPlayingWhite &&
4180                           appData.premoveWhite) {
4181                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4182                         if (appData.debugMode)
4183                           fprintf(debugFP, "Sending premove:\n");
4184                         SendToICS(str);
4185                       } else if (currentMove == 1 &&
4186                                  gameMode == IcsPlayingBlack &&
4187                                  appData.premoveBlack) {
4188                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4189                         if (appData.debugMode)
4190                           fprintf(debugFP, "Sending premove:\n");
4191                         SendToICS(str);
4192                       } else if (gotPremove) {
4193                         int oldFMM = forwardMostMove;
4194                         gotPremove = 0;
4195                         ClearPremoveHighlights();
4196                         if (appData.debugMode)
4197                           fprintf(debugFP, "Sending premove:\n");
4198                           UserMoveEvent(premoveFromX, premoveFromY,
4199                                         premoveToX, premoveToY,
4200                                         premovePromoChar);
4201                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4202                           if(moveList[oldFMM-1][1] != '@')
4203                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4204                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4205                           else // (drop)
4206                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4207                         }
4208                       }
4209                     }
4210
4211                     /* Usually suppress following prompt */
4212                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4213                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4214                         if (looking_at(buf, &i, "*% ")) {
4215                             savingComment = FALSE;
4216                             suppressKibitz = 0;
4217                         }
4218                     }
4219                     next_out = i;
4220                 } else if (started == STARTED_HOLDINGS) {
4221                     int gamenum;
4222                     char new_piece[MSG_SIZ];
4223                     started = STARTED_NONE;
4224                     parse[parse_pos] = NULLCHAR;
4225                     if (appData.debugMode)
4226                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4227                                                         parse, currentMove);
4228                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4229                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4230                         new_piece[0] = NULLCHAR;
4231                         sscanf(parse, "game %d white [%s black [%s <- %s",
4232                                &gamenum, white_holding, black_holding,
4233                                new_piece);
4234                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4235                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4236                         if (gameInfo.variant == VariantNormal) {
4237                           /* [HGM] We seem to switch variant during a game!
4238                            * Presumably no holdings were displayed, so we have
4239                            * to move the position two files to the right to
4240                            * create room for them!
4241                            */
4242                           VariantClass newVariant;
4243                           switch(gameInfo.boardWidth) { // base guess on board width
4244                                 case 9:  newVariant = VariantShogi; break;
4245                                 case 10: newVariant = VariantGreat; break;
4246                                 default: newVariant = VariantCrazyhouse;
4247                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4248                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4249                                          newVariant = VariantSChess;
4250                           }
4251                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4252                           /* Get a move list just to see the header, which
4253                              will tell us whether this is really bug or zh */
4254                           if (ics_getting_history == H_FALSE) {
4255                             ics_getting_history = H_REQUESTED;
4256                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4257                             SendToICS(str);
4258                           }
4259                         }
4260                         /* [HGM] copy holdings to board holdings area */
4261                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4262                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4263                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4264 #if ZIPPY
4265                         if (appData.zippyPlay && first.initDone) {
4266                             ZippyHoldings(white_holding, black_holding,
4267                                           new_piece);
4268                         }
4269 #endif /*ZIPPY*/
4270                         if (tinyLayout || smallLayout) {
4271                             char wh[16], bh[16];
4272                             PackHolding(wh, white_holding);
4273                             PackHolding(bh, black_holding);
4274                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4275                                     gameInfo.white, gameInfo.black);
4276                         } else {
4277                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4278                                     gameInfo.white, white_holding, _("vs."),
4279                                     gameInfo.black, black_holding);
4280                         }
4281                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4282                         DrawPosition(FALSE, boards[currentMove]);
4283                         DisplayTitle(str);
4284                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4285                         sscanf(parse, "game %d white [%s black [%s <- %s",
4286                                &gamenum, white_holding, black_holding,
4287                                new_piece);
4288                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4289                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4290                         /* [HGM] copy holdings to partner-board holdings area */
4291                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4292                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4293                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4294                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4295                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4296                       }
4297                     }
4298                     /* Suppress following prompt */
4299                     if (looking_at(buf, &i, "*% ")) {
4300                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4301                         savingComment = FALSE;
4302                         suppressKibitz = 0;
4303                     }
4304                     next_out = i;
4305                 }
4306                 continue;
4307             }
4308
4309             i++;                /* skip unparsed character and loop back */
4310         }
4311
4312         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4313 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4314 //          SendToPlayer(&buf[next_out], i - next_out);
4315             started != STARTED_HOLDINGS && leftover_start > next_out) {
4316             SendToPlayer(&buf[next_out], leftover_start - next_out);
4317             next_out = i;
4318         }
4319
4320         leftover_len = buf_len - leftover_start;
4321         /* if buffer ends with something we couldn't parse,
4322            reparse it after appending the next read */
4323
4324     } else if (count == 0) {
4325         RemoveInputSource(isr);
4326         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4327     } else {
4328         DisplayFatalError(_("Error reading from ICS"), error, 1);
4329     }
4330 }
4331
4332
4333 /* Board style 12 looks like this:
4334
4335    <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
4336
4337  * The "<12> " is stripped before it gets to this routine.  The two
4338  * trailing 0's (flip state and clock ticking) are later addition, and
4339  * some chess servers may not have them, or may have only the first.
4340  * Additional trailing fields may be added in the future.
4341  */
4342
4343 #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"
4344
4345 #define RELATION_OBSERVING_PLAYED    0
4346 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4347 #define RELATION_PLAYING_MYMOVE      1
4348 #define RELATION_PLAYING_NOTMYMOVE  -1
4349 #define RELATION_EXAMINING           2
4350 #define RELATION_ISOLATED_BOARD     -3
4351 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4352
4353 void
4354 ParseBoard12 (char *string)
4355 {
4356 #if ZIPPY
4357     int i, takeback;
4358     char *bookHit = NULL; // [HGM] book
4359 #endif
4360     GameMode newGameMode;
4361     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4362     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4363     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4364     char to_play, board_chars[200];
4365     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4366     char black[32], white[32];
4367     Board board;
4368     int prevMove = currentMove;
4369     int ticking = 2;
4370     ChessMove moveType;
4371     int fromX, fromY, toX, toY;
4372     char promoChar;
4373     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4374     Boolean weird = FALSE, reqFlag = FALSE;
4375
4376     fromX = fromY = toX = toY = -1;
4377
4378     newGame = FALSE;
4379
4380     if (appData.debugMode)
4381       fprintf(debugFP, "Parsing board: %s\n", string);
4382
4383     move_str[0] = NULLCHAR;
4384     elapsed_time[0] = NULLCHAR;
4385     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4386         int  i = 0, j;
4387         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4388             if(string[i] == ' ') { ranks++; files = 0; }
4389             else files++;
4390             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4391             i++;
4392         }
4393         for(j = 0; j <i; j++) board_chars[j] = string[j];
4394         board_chars[i] = '\0';
4395         string += i + 1;
4396     }
4397     n = sscanf(string, PATTERN, &to_play, &double_push,
4398                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4399                &gamenum, white, black, &relation, &basetime, &increment,
4400                &white_stren, &black_stren, &white_time, &black_time,
4401                &moveNum, str, elapsed_time, move_str, &ics_flip,
4402                &ticking);
4403
4404     if (n < 21) {
4405         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4406         DisplayError(str, 0);
4407         return;
4408     }
4409
4410     /* Convert the move number to internal form */
4411     moveNum = (moveNum - 1) * 2;
4412     if (to_play == 'B') moveNum++;
4413     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4414       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4415                         0, 1);
4416       return;
4417     }
4418
4419     switch (relation) {
4420       case RELATION_OBSERVING_PLAYED:
4421       case RELATION_OBSERVING_STATIC:
4422         if (gamenum == -1) {
4423             /* Old ICC buglet */
4424             relation = RELATION_OBSERVING_STATIC;
4425         }
4426         newGameMode = IcsObserving;
4427         break;
4428       case RELATION_PLAYING_MYMOVE:
4429       case RELATION_PLAYING_NOTMYMOVE:
4430         newGameMode =
4431           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4432             IcsPlayingWhite : IcsPlayingBlack;
4433         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4434         break;
4435       case RELATION_EXAMINING:
4436         newGameMode = IcsExamining;
4437         break;
4438       case RELATION_ISOLATED_BOARD:
4439       default:
4440         /* Just display this board.  If user was doing something else,
4441            we will forget about it until the next board comes. */
4442         newGameMode = IcsIdle;
4443         break;
4444       case RELATION_STARTING_POSITION:
4445         newGameMode = gameMode;
4446         break;
4447     }
4448
4449     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4450         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4451          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4452       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4453       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4454       static int lastBgGame = -1;
4455       char *toSqr;
4456       for (k = 0; k < ranks; k++) {
4457         for (j = 0; j < files; j++)
4458           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4459         if(gameInfo.holdingsWidth > 1) {
4460              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4461              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4462         }
4463       }
4464       CopyBoard(partnerBoard, board);
4465       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4466         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4467         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4468       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4469       if(toSqr = strchr(str, '-')) {
4470         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4471         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4472       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4473       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4474       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4475       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4476       if(twoBoards) {
4477           DisplayWhiteClock(white_time*fac, to_play == 'W');
4478           DisplayBlackClock(black_time*fac, to_play != 'W');
4479           activePartner = to_play;
4480           if(gamenum != lastBgGame) {
4481               char buf[MSG_SIZ];
4482               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4483               DisplayTitle(buf);
4484           }
4485           lastBgGame = gamenum;
4486           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4487                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4488       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4489                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4490       if(!twoBoards) DisplayMessage(partnerStatus, "");
4491         partnerBoardValid = TRUE;
4492       return;
4493     }
4494
4495     if(appData.dualBoard && appData.bgObserve) {
4496         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4497             SendToICS(ics_prefix), SendToICS("pobserve\n");
4498         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4499             char buf[MSG_SIZ];
4500             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4501             SendToICS(buf);
4502         }
4503     }
4504
4505     /* Modify behavior for initial board display on move listing
4506        of wild games.
4507        */
4508     switch (ics_getting_history) {
4509       case H_FALSE:
4510       case H_REQUESTED:
4511         break;
4512       case H_GOT_REQ_HEADER:
4513       case H_GOT_UNREQ_HEADER:
4514         /* This is the initial position of the current game */
4515         gamenum = ics_gamenum;
4516         moveNum = 0;            /* old ICS bug workaround */
4517         if (to_play == 'B') {
4518           startedFromSetupPosition = TRUE;
4519           blackPlaysFirst = TRUE;
4520           moveNum = 1;
4521           if (forwardMostMove == 0) forwardMostMove = 1;
4522           if (backwardMostMove == 0) backwardMostMove = 1;
4523           if (currentMove == 0) currentMove = 1;
4524         }
4525         newGameMode = gameMode;
4526         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4527         break;
4528       case H_GOT_UNWANTED_HEADER:
4529         /* This is an initial board that we don't want */
4530         return;
4531       case H_GETTING_MOVES:
4532         /* Should not happen */
4533         DisplayError(_("Error gathering move list: extra board"), 0);
4534         ics_getting_history = H_FALSE;
4535         return;
4536     }
4537
4538    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4539                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4540                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4541      /* [HGM] We seem to have switched variant unexpectedly
4542       * Try to guess new variant from board size
4543       */
4544           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4545           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4546           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4547           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4548           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4549           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4550           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4551           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4552           /* Get a move list just to see the header, which
4553              will tell us whether this is really bug or zh */
4554           if (ics_getting_history == H_FALSE) {
4555             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4556             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4557             SendToICS(str);
4558           }
4559     }
4560
4561     /* Take action if this is the first board of a new game, or of a
4562        different game than is currently being displayed.  */
4563     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4564         relation == RELATION_ISOLATED_BOARD) {
4565
4566         /* Forget the old game and get the history (if any) of the new one */
4567         if (gameMode != BeginningOfGame) {
4568           Reset(TRUE, TRUE);
4569         }
4570         newGame = TRUE;
4571         if (appData.autoRaiseBoard) BoardToTop();
4572         prevMove = -3;
4573         if (gamenum == -1) {
4574             newGameMode = IcsIdle;
4575         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4576                    appData.getMoveList && !reqFlag) {
4577             /* Need to get game history */
4578             ics_getting_history = H_REQUESTED;
4579             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4580             SendToICS(str);
4581         }
4582
4583         /* Initially flip the board to have black on the bottom if playing
4584            black or if the ICS flip flag is set, but let the user change
4585            it with the Flip View button. */
4586         flipView = appData.autoFlipView ?
4587           (newGameMode == IcsPlayingBlack) || ics_flip :
4588           appData.flipView;
4589
4590         /* Done with values from previous mode; copy in new ones */
4591         gameMode = newGameMode;
4592         ModeHighlight();
4593         ics_gamenum = gamenum;
4594         if (gamenum == gs_gamenum) {
4595             int klen = strlen(gs_kind);
4596             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4597             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4598             gameInfo.event = StrSave(str);
4599         } else {
4600             gameInfo.event = StrSave("ICS game");
4601         }
4602         gameInfo.site = StrSave(appData.icsHost);
4603         gameInfo.date = PGNDate();
4604         gameInfo.round = StrSave("-");
4605         gameInfo.white = StrSave(white);
4606         gameInfo.black = StrSave(black);
4607         timeControl = basetime * 60 * 1000;
4608         timeControl_2 = 0;
4609         timeIncrement = increment * 1000;
4610         movesPerSession = 0;
4611         gameInfo.timeControl = TimeControlTagValue();
4612         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4613   if (appData.debugMode) {
4614     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4615     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4616     setbuf(debugFP, NULL);
4617   }
4618
4619         gameInfo.outOfBook = NULL;
4620
4621         /* Do we have the ratings? */
4622         if (strcmp(player1Name, white) == 0 &&
4623             strcmp(player2Name, black) == 0) {
4624             if (appData.debugMode)
4625               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4626                       player1Rating, player2Rating);
4627             gameInfo.whiteRating = player1Rating;
4628             gameInfo.blackRating = player2Rating;
4629         } else if (strcmp(player2Name, white) == 0 &&
4630                    strcmp(player1Name, black) == 0) {
4631             if (appData.debugMode)
4632               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4633                       player2Rating, player1Rating);
4634             gameInfo.whiteRating = player2Rating;
4635             gameInfo.blackRating = player1Rating;
4636         }
4637         player1Name[0] = player2Name[0] = NULLCHAR;
4638
4639         /* Silence shouts if requested */
4640         if (appData.quietPlay &&
4641             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4642             SendToICS(ics_prefix);
4643             SendToICS("set shout 0\n");
4644         }
4645     }
4646
4647     /* Deal with midgame name changes */
4648     if (!newGame) {
4649         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4650             if (gameInfo.white) free(gameInfo.white);
4651             gameInfo.white = StrSave(white);
4652         }
4653         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4654             if (gameInfo.black) free(gameInfo.black);
4655             gameInfo.black = StrSave(black);
4656         }
4657     }
4658
4659     /* Throw away game result if anything actually changes in examine mode */
4660     if (gameMode == IcsExamining && !newGame) {
4661         gameInfo.result = GameUnfinished;
4662         if (gameInfo.resultDetails != NULL) {
4663             free(gameInfo.resultDetails);
4664             gameInfo.resultDetails = NULL;
4665         }
4666     }
4667
4668     /* In pausing && IcsExamining mode, we ignore boards coming
4669        in if they are in a different variation than we are. */
4670     if (pauseExamInvalid) return;
4671     if (pausing && gameMode == IcsExamining) {
4672         if (moveNum <= pauseExamForwardMostMove) {
4673             pauseExamInvalid = TRUE;
4674             forwardMostMove = pauseExamForwardMostMove;
4675             return;
4676         }
4677     }
4678
4679   if (appData.debugMode) {
4680     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4681   }
4682     /* Parse the board */
4683     for (k = 0; k < ranks; k++) {
4684       for (j = 0; j < files; j++)
4685         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4686       if(gameInfo.holdingsWidth > 1) {
4687            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4688            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4689       }
4690     }
4691     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4692       board[5][BOARD_RGHT+1] = WhiteAngel;
4693       board[6][BOARD_RGHT+1] = WhiteMarshall;
4694       board[1][0] = BlackMarshall;
4695       board[2][0] = BlackAngel;
4696       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4697     }
4698     CopyBoard(boards[moveNum], board);
4699     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4700     if (moveNum == 0) {
4701         startedFromSetupPosition =
4702           !CompareBoards(board, initialPosition);
4703         if(startedFromSetupPosition)
4704             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4705     }
4706
4707     /* [HGM] Set castling rights. Take the outermost Rooks,
4708        to make it also work for FRC opening positions. Note that board12
4709        is really defective for later FRC positions, as it has no way to
4710        indicate which Rook can castle if they are on the same side of King.
4711        For the initial position we grant rights to the outermost Rooks,
4712        and remember thos rights, and we then copy them on positions
4713        later in an FRC game. This means WB might not recognize castlings with
4714        Rooks that have moved back to their original position as illegal,
4715        but in ICS mode that is not its job anyway.
4716     */
4717     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4718     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4719
4720         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4721             if(board[0][i] == WhiteRook) j = i;
4722         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4723         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4724             if(board[0][i] == WhiteRook) j = i;
4725         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4726         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4727             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4728         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4729         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4730             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4731         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4732
4733         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4734         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4735         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4736             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4737         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4738             if(board[BOARD_HEIGHT-1][k] == bKing)
4739                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4740         if(gameInfo.variant == VariantTwoKings) {
4741             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4742             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4743             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4744         }
4745     } else { int r;
4746         r = boards[moveNum][CASTLING][0] = initialRights[0];
4747         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4748         r = boards[moveNum][CASTLING][1] = initialRights[1];
4749         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4750         r = boards[moveNum][CASTLING][3] = initialRights[3];
4751         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4752         r = boards[moveNum][CASTLING][4] = initialRights[4];
4753         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4754         /* wildcastle kludge: always assume King has rights */
4755         r = boards[moveNum][CASTLING][2] = initialRights[2];
4756         r = boards[moveNum][CASTLING][5] = initialRights[5];
4757     }
4758     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4759     boards[moveNum][EP_STATUS] = EP_NONE;
4760     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4761     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4762     if(double_push !=  -1) {
4763         int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4764         boards[moveNum][EP_FILE] = // also set new e.p. variables
4765         boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4766         boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4767         boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4768     } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4769
4770
4771     if (ics_getting_history == H_GOT_REQ_HEADER ||
4772         ics_getting_history == H_GOT_UNREQ_HEADER) {
4773         /* This was an initial position from a move list, not
4774            the current position */
4775         return;
4776     }
4777
4778     /* Update currentMove and known move number limits */
4779     newMove = newGame || moveNum > forwardMostMove;
4780
4781     if (newGame) {
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783         if (gameMode == IcsExamining && moveNum == 0) {
4784           /* Workaround for ICS limitation: we are not told the wild
4785              type when starting to examine a game.  But if we ask for
4786              the move list, the move list header will tell us */
4787             ics_getting_history = H_REQUESTED;
4788             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4789             SendToICS(str);
4790         }
4791     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4792                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4793 #if ZIPPY
4794         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4795         /* [HGM] applied this also to an engine that is silently watching        */
4796         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4797             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4798             gameInfo.variant == currentlyInitializedVariant) {
4799           takeback = forwardMostMove - moveNum;
4800           for (i = 0; i < takeback; i++) {
4801             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4802             SendToProgram("undo\n", &first);
4803           }
4804         }
4805 #endif
4806
4807         forwardMostMove = moveNum;
4808         if (!pausing || currentMove > forwardMostMove)
4809           currentMove = forwardMostMove;
4810     } else {
4811         /* New part of history that is not contiguous with old part */
4812         if (pausing && gameMode == IcsExamining) {
4813             pauseExamInvalid = TRUE;
4814             forwardMostMove = pauseExamForwardMostMove;
4815             return;
4816         }
4817         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4818 #if ZIPPY
4819             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4820                 // [HGM] when we will receive the move list we now request, it will be
4821                 // fed to the engine from the first move on. So if the engine is not
4822                 // in the initial position now, bring it there.
4823                 InitChessProgram(&first, 0);
4824             }
4825 #endif
4826             ics_getting_history = H_REQUESTED;
4827             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4828             SendToICS(str);
4829         }
4830         forwardMostMove = backwardMostMove = currentMove = moveNum;
4831     }
4832
4833     /* Update the clocks */
4834     if (strchr(elapsed_time, '.')) {
4835       /* Time is in ms */
4836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4838     } else {
4839       /* Time is in seconds */
4840       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4841       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4842     }
4843
4844
4845 #if ZIPPY
4846     if (appData.zippyPlay && newGame &&
4847         gameMode != IcsObserving && gameMode != IcsIdle &&
4848         gameMode != IcsExamining)
4849       ZippyFirstBoard(moveNum, basetime, increment);
4850 #endif
4851
4852     /* Put the move on the move list, first converting
4853        to canonical algebraic form. */
4854     if (moveNum > 0) {
4855   if (appData.debugMode) {
4856     int f = forwardMostMove;
4857     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4858             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4859             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4860     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4861     fprintf(debugFP, "moveNum = %d\n", moveNum);
4862     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4863     setbuf(debugFP, NULL);
4864   }
4865         if (moveNum <= backwardMostMove) {
4866             /* We don't know what the board looked like before
4867                this move.  Punt. */
4868           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4869             strcat(parseList[moveNum - 1], " ");
4870             strcat(parseList[moveNum - 1], elapsed_time);
4871             moveList[moveNum - 1][0] = NULLCHAR;
4872         } else if (strcmp(move_str, "none") == 0) {
4873             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4874             /* Again, we don't know what the board looked like;
4875                this is really the start of the game. */
4876             parseList[moveNum - 1][0] = NULLCHAR;
4877             moveList[moveNum - 1][0] = NULLCHAR;
4878             backwardMostMove = moveNum;
4879             startedFromSetupPosition = TRUE;
4880             fromX = fromY = toX = toY = -1;
4881         } else {
4882           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4883           //                 So we parse the long-algebraic move string in stead of the SAN move
4884           int valid; char buf[MSG_SIZ], *prom;
4885
4886           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4887                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4888           // str looks something like "Q/a1-a2"; kill the slash
4889           if(str[1] == '/')
4890             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4891           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4892           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4893                 strcat(buf, prom); // long move lacks promo specification!
4894           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4895                 if(appData.debugMode)
4896                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4897                 safeStrCpy(move_str, buf, MSG_SIZ);
4898           }
4899           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4900                                 &fromX, &fromY, &toX, &toY, &promoChar)
4901                || ParseOneMove(buf, moveNum - 1, &moveType,
4902                                 &fromX, &fromY, &toX, &toY, &promoChar);
4903           // end of long SAN patch
4904           if (valid) {
4905             (void) CoordsToAlgebraic(boards[moveNum - 1],
4906                                      PosFlags(moveNum - 1),
4907                                      fromY, fromX, toY, toX, promoChar,
4908                                      parseList[moveNum-1]);
4909             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4910               case MT_NONE:
4911               case MT_STALEMATE:
4912               default:
4913                 break;
4914               case MT_CHECK:
4915                 if(!IS_SHOGI(gameInfo.variant))
4916                     strcat(parseList[moveNum - 1], "+");
4917                 break;
4918               case MT_CHECKMATE:
4919               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4920                 strcat(parseList[moveNum - 1], "#");
4921                 break;
4922             }
4923             strcat(parseList[moveNum - 1], " ");
4924             strcat(parseList[moveNum - 1], elapsed_time);
4925             /* currentMoveString is set as a side-effect of ParseOneMove */
4926             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4927             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4928             strcat(moveList[moveNum - 1], "\n");
4929
4930             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4931                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4932               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4933                 ChessSquare old, new = boards[moveNum][k][j];
4934                   if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4935                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4936                   if(old == new) continue;
4937                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4938                   else if(new == WhiteWazir || new == BlackWazir) {
4939                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4940                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4941                       else boards[moveNum][k][j] = old; // preserve type of Gold
4942                   } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4943                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4944               }
4945           } else {
4946             /* Move from ICS was illegal!?  Punt. */
4947             if (appData.debugMode) {
4948               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4949               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4950             }
4951             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4952             strcat(parseList[moveNum - 1], " ");
4953             strcat(parseList[moveNum - 1], elapsed_time);
4954             moveList[moveNum - 1][0] = NULLCHAR;
4955             fromX = fromY = toX = toY = -1;
4956           }
4957         }
4958   if (appData.debugMode) {
4959     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4960     setbuf(debugFP, NULL);
4961   }
4962
4963 #if ZIPPY
4964         /* Send move to chess program (BEFORE animating it). */
4965         if (appData.zippyPlay && !newGame && newMove &&
4966            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4967
4968             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4969                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4970                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4971                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4972                             move_str);
4973                     DisplayError(str, 0);
4974                 } else {
4975                     if (first.sendTime) {
4976                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4977                     }
4978                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4979                     if (firstMove && !bookHit) {
4980                         firstMove = FALSE;
4981                         if (first.useColors) {
4982                           SendToProgram(gameMode == IcsPlayingWhite ?
4983                                         "white\ngo\n" :
4984                                         "black\ngo\n", &first);
4985                         } else {
4986                           SendToProgram("go\n", &first);
4987                         }
4988                         first.maybeThinking = TRUE;
4989                     }
4990                 }
4991             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4992               if (moveList[moveNum - 1][0] == NULLCHAR) {
4993                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4994                 DisplayError(str, 0);
4995               } else {
4996                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4997                 SendMoveToProgram(moveNum - 1, &first);
4998               }
4999             }
5000         }
5001 #endif
5002     }
5003
5004     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
5005         /* If move comes from a remote source, animate it.  If it
5006            isn't remote, it will have already been animated. */
5007         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
5008             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
5009         }
5010         if (!pausing && appData.highlightLastMove) {
5011             SetHighlights(fromX, fromY, toX, toY);
5012         }
5013     }
5014
5015     /* Start the clocks */
5016     whiteFlag = blackFlag = FALSE;
5017     appData.clockMode = !(basetime == 0 && increment == 0);
5018     if (ticking == 0) {
5019       ics_clock_paused = TRUE;
5020       StopClocks();
5021     } else if (ticking == 1) {
5022       ics_clock_paused = FALSE;
5023     }
5024     if (gameMode == IcsIdle ||
5025         relation == RELATION_OBSERVING_STATIC ||
5026         relation == RELATION_EXAMINING ||
5027         ics_clock_paused)
5028       DisplayBothClocks();
5029     else
5030       StartClocks();
5031
5032     /* Display opponents and material strengths */
5033     if (gameInfo.variant != VariantBughouse &&
5034         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5035         if (tinyLayout || smallLayout) {
5036             if(gameInfo.variant == VariantNormal)
5037               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5038                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5039                     basetime, increment);
5040             else
5041               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5042                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5043                     basetime, increment, (int) gameInfo.variant);
5044         } else {
5045             if(gameInfo.variant == VariantNormal)
5046               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5047                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5048                     basetime, increment);
5049             else
5050               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5051                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5052                     basetime, increment, VariantName(gameInfo.variant));
5053         }
5054         DisplayTitle(str);
5055   if (appData.debugMode) {
5056     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5057   }
5058     }
5059
5060
5061     /* Display the board */
5062     if (!pausing && !appData.noGUI) {
5063
5064       if (appData.premove)
5065           if (!gotPremove ||
5066              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5067              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5068               ClearPremoveHighlights();
5069
5070       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5071         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5072       DrawPosition(j, boards[currentMove]);
5073
5074       DisplayMove(moveNum - 1);
5075       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5076             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5077               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5078         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5079       }
5080     }
5081
5082     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5083 #if ZIPPY
5084     if(bookHit) { // [HGM] book: simulate book reply
5085         static char bookMove[MSG_SIZ]; // a bit generous?
5086
5087         programStats.nodes = programStats.depth = programStats.time =
5088         programStats.score = programStats.got_only_move = 0;
5089         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5090
5091         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5092         strcat(bookMove, bookHit);
5093         HandleMachineMove(bookMove, &first);
5094     }
5095 #endif
5096 }
5097
5098 void
5099 GetMoveListEvent ()
5100 {
5101     char buf[MSG_SIZ];
5102     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5103         ics_getting_history = H_REQUESTED;
5104         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5105         SendToICS(buf);
5106     }
5107 }
5108
5109 void
5110 SendToBoth (char *msg)
5111 {   // to make it easy to keep two engines in step in dual analysis
5112     SendToProgram(msg, &first);
5113     if(second.analyzing) SendToProgram(msg, &second);
5114 }
5115
5116 void
5117 AnalysisPeriodicEvent (int force)
5118 {
5119     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5120          && !force) || !appData.periodicUpdates)
5121       return;
5122
5123     /* Send . command to Crafty to collect stats */
5124     SendToBoth(".\n");
5125
5126     /* Don't send another until we get a response (this makes
5127        us stop sending to old Crafty's which don't understand
5128        the "." command (sending illegal cmds resets node count & time,
5129        which looks bad)) */
5130     programStats.ok_to_send = 0;
5131 }
5132
5133 void
5134 ics_update_width (int new_width)
5135 {
5136         ics_printf("set width %d\n", new_width);
5137 }
5138
5139 void
5140 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5141 {
5142     char buf[MSG_SIZ];
5143
5144     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5145         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5146             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5147             SendToProgram(buf, cps);
5148             return;
5149         }
5150         // null move in variant where engine does not understand it (for analysis purposes)
5151         SendBoard(cps, moveNum + 1); // send position after move in stead.
5152         return;
5153     }
5154     if (cps->useUsermove) {
5155       SendToProgram("usermove ", cps);
5156     }
5157     if (cps->useSAN) {
5158       char *space;
5159       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5160         int len = space - parseList[moveNum];
5161         memcpy(buf, parseList[moveNum], len);
5162         buf[len++] = '\n';
5163         buf[len] = NULLCHAR;
5164       } else {
5165         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5166       }
5167       SendToProgram(buf, cps);
5168     } else {
5169       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5170         AlphaRank(moveList[moveNum], 4);
5171         SendToProgram(moveList[moveNum], cps);
5172         AlphaRank(moveList[moveNum], 4); // and back
5173       } else
5174       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5175        * the engine. It would be nice to have a better way to identify castle
5176        * moves here. */
5177       if(appData.fischerCastling && cps->useOOCastle) {
5178         int fromX = moveList[moveNum][0] - AAA;
5179         int fromY = moveList[moveNum][1] - ONE;
5180         int toX = moveList[moveNum][2] - AAA;
5181         int toY = moveList[moveNum][3] - ONE;
5182         if((boards[moveNum][fromY][fromX] == WhiteKing
5183             && boards[moveNum][toY][toX] == WhiteRook)
5184            || (boards[moveNum][fromY][fromX] == BlackKing
5185                && boards[moveNum][toY][toX] == BlackRook)) {
5186           if(toX > fromX) SendToProgram("O-O\n", cps);
5187           else SendToProgram("O-O-O\n", cps);
5188         }
5189         else SendToProgram(moveList[moveNum], cps);
5190       } else
5191       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5192         char *m = moveList[moveNum];
5193         static char c[2];
5194         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5195         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
5196           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5197                                                m[2], m[3] - '0',
5198                                                m[5], m[6] - '0',
5199                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5200         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5201           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5202           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5203                                                m[7], m[8] - '0',
5204                                                m[7], m[8] - '0',
5205                                                m[5], m[6] - '0',
5206                                                m[5], m[6] - '0',
5207                                                m[2], m[3] - '0', c);
5208         } else
5209           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5210                                                m[5], m[6] - '0',
5211                                                m[5], m[6] - '0',
5212                                                m[2], m[3] - '0', c);
5213           SendToProgram(buf, cps);
5214       } else
5215       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5216         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5217           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5218           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5219                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5220         } else
5221           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5222                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5223         SendToProgram(buf, cps);
5224       }
5225       else SendToProgram(moveList[moveNum], cps);
5226       /* End of additions by Tord */
5227     }
5228
5229     /* [HGM] setting up the opening has brought engine in force mode! */
5230     /*       Send 'go' if we are in a mode where machine should play. */
5231     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5232         (gameMode == TwoMachinesPlay   ||
5233 #if ZIPPY
5234          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5235 #endif
5236          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5237         SendToProgram("go\n", cps);
5238   if (appData.debugMode) {
5239     fprintf(debugFP, "(extra)\n");
5240   }
5241     }
5242     setboardSpoiledMachineBlack = 0;
5243 }
5244
5245 void
5246 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5247 {
5248     char user_move[MSG_SIZ];
5249     char suffix[4];
5250
5251     if(gameInfo.variant == VariantSChess && promoChar) {
5252         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5253         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5254     } else suffix[0] = NULLCHAR;
5255
5256     switch (moveType) {
5257       default:
5258         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5259                 (int)moveType, fromX, fromY, toX, toY);
5260         DisplayError(user_move + strlen("say "), 0);
5261         break;
5262       case WhiteKingSideCastle:
5263       case BlackKingSideCastle:
5264       case WhiteQueenSideCastleWild:
5265       case BlackQueenSideCastleWild:
5266       /* PUSH Fabien */
5267       case WhiteHSideCastleFR:
5268       case BlackHSideCastleFR:
5269       /* POP Fabien */
5270         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5271         break;
5272       case WhiteQueenSideCastle:
5273       case BlackQueenSideCastle:
5274       case WhiteKingSideCastleWild:
5275       case BlackKingSideCastleWild:
5276       /* PUSH Fabien */
5277       case WhiteASideCastleFR:
5278       case BlackASideCastleFR:
5279       /* POP Fabien */
5280         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5281         break;
5282       case WhiteNonPromotion:
5283       case BlackNonPromotion:
5284         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5285         break;
5286       case WhitePromotion:
5287       case BlackPromotion:
5288         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5289            gameInfo.variant == VariantMakruk)
5290           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5291                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5292                 PieceToChar(WhiteFerz));
5293         else if(gameInfo.variant == VariantGreat)
5294           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5295                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5296                 PieceToChar(WhiteMan));
5297         else
5298           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5299                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5300                 promoChar);
5301         break;
5302       case WhiteDrop:
5303       case BlackDrop:
5304       drop:
5305         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5306                  ToUpper(PieceToChar((ChessSquare) fromX)),
5307                  AAA + toX, ONE + toY);
5308         break;
5309       case IllegalMove:  /* could be a variant we don't quite understand */
5310         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5311       case NormalMove:
5312       case WhiteCapturesEnPassant:
5313       case BlackCapturesEnPassant:
5314         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5315                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5316         break;
5317     }
5318     SendToICS(user_move);
5319     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5320         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5321 }
5322
5323 void
5324 UploadGameEvent ()
5325 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5326     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5327     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5328     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5329       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5330       return;
5331     }
5332     if(gameMode != IcsExamining) { // is this ever not the case?
5333         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5334
5335         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5336           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5337         } else { // on FICS we must first go to general examine mode
5338           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5339         }
5340         if(gameInfo.variant != VariantNormal) {
5341             // try figure out wild number, as xboard names are not always valid on ICS
5342             for(i=1; i<=36; i++) {
5343               snprintf(buf, MSG_SIZ, "wild/%d", i);
5344                 if(StringToVariant(buf) == gameInfo.variant) break;
5345             }
5346             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5347             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5348             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5349         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5350         SendToICS(ics_prefix);
5351         SendToICS(buf);
5352         if(startedFromSetupPosition || backwardMostMove != 0) {
5353           fen = PositionToFEN(backwardMostMove, NULL, 1);
5354           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5355             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5356             SendToICS(buf);
5357           } else { // FICS: everything has to set by separate bsetup commands
5358             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5359             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5360             SendToICS(buf);
5361             if(!WhiteOnMove(backwardMostMove)) {
5362                 SendToICS("bsetup tomove black\n");
5363             }
5364             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5365             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5366             SendToICS(buf);
5367             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5368             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5369             SendToICS(buf);
5370             i = boards[backwardMostMove][EP_STATUS];
5371             if(i >= 0) { // set e.p.
5372               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5373                 SendToICS(buf);
5374             }
5375             bsetup++;
5376           }
5377         }
5378       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5379             SendToICS("bsetup done\n"); // switch to normal examining.
5380     }
5381     for(i = backwardMostMove; i<last; i++) {
5382         char buf[20];
5383         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5384         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5385             int len = strlen(moveList[i]);
5386             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5387             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5388         }
5389         SendToICS(buf);
5390     }
5391     SendToICS(ics_prefix);
5392     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5393 }
5394
5395 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5396 int legNr = 1;
5397
5398 void
5399 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5400 {
5401     if (rf == DROP_RANK) {
5402       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5403       sprintf(move, "%c@%c%c\n",
5404                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5405     } else {
5406         if (promoChar == 'x' || promoChar == NULLCHAR) {
5407           sprintf(move, "%c%c%c%c\n",
5408                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5409           if(killX >= 0 && killY >= 0) {
5410             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5411             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5412           }
5413         } else {
5414             sprintf(move, "%c%c%c%c%c\n",
5415                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5416           if(killX >= 0 && killY >= 0) {
5417             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5418             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5419           }
5420         }
5421     }
5422 }
5423
5424 void
5425 ProcessICSInitScript (FILE *f)
5426 {
5427     char buf[MSG_SIZ];
5428
5429     while (fgets(buf, MSG_SIZ, f)) {
5430         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5431     }
5432
5433     fclose(f);
5434 }
5435
5436
5437 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5438 int dragging;
5439 static ClickType lastClickType;
5440
5441 int
5442 PieceInString (char *s, ChessSquare piece)
5443 {
5444   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5445   while((p = strchr(s, ID))) {
5446     if(!suffix || p[1] == suffix) return TRUE;
5447     s = p;
5448   }
5449   return FALSE;
5450 }
5451
5452 int
5453 Partner (ChessSquare *p)
5454 { // change piece into promotion partner if one shogi-promotes to the other
5455   ChessSquare partner = promoPartner[*p];
5456   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5457   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5458   *p = partner;
5459   return 1;
5460 }
5461
5462 void
5463 Sweep (int step)
5464 {
5465     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5466     static int toggleFlag;
5467     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5468     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5469     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5470     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5471     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5472     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5473     do {
5474         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5475         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5476         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5477         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5478         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5479         if(!step) step = -1;
5480     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5481             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5482             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5483             promoSweep == pawn ||
5484             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5485             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5486     if(toX >= 0) {
5487         int victim = boards[currentMove][toY][toX];
5488         boards[currentMove][toY][toX] = promoSweep;
5489         DrawPosition(FALSE, boards[currentMove]);
5490         boards[currentMove][toY][toX] = victim;
5491     } else
5492     ChangeDragPiece(promoSweep);
5493 }
5494
5495 int
5496 PromoScroll (int x, int y)
5497 {
5498   int step = 0;
5499
5500   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5501   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5502   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5503   if(!step) return FALSE;
5504   lastX = x; lastY = y;
5505   if((promoSweep < BlackPawn) == flipView) step = -step;
5506   if(step > 0) selectFlag = 1;
5507   if(!selectFlag) Sweep(step);
5508   return FALSE;
5509 }
5510
5511 void
5512 NextPiece (int step)
5513 {
5514     ChessSquare piece = boards[currentMove][toY][toX];
5515     do {
5516         pieceSweep -= step;
5517         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5518         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5519         if(!step) step = -1;
5520     } while(PieceToChar(pieceSweep) == '.');
5521     boards[currentMove][toY][toX] = pieceSweep;
5522     DrawPosition(FALSE, boards[currentMove]);
5523     boards[currentMove][toY][toX] = piece;
5524 }
5525 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5526 void
5527 AlphaRank (char *move, int n)
5528 {
5529 //    char *p = move, c; int x, y;
5530
5531     if (appData.debugMode) {
5532         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5533     }
5534
5535     if(move[1]=='*' &&
5536        move[2]>='0' && move[2]<='9' &&
5537        move[3]>='a' && move[3]<='x'    ) {
5538         move[1] = '@';
5539         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5540         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5541     } else
5542     if(move[0]>='0' && move[0]<='9' &&
5543        move[1]>='a' && move[1]<='x' &&
5544        move[2]>='0' && move[2]<='9' &&
5545        move[3]>='a' && move[3]<='x'    ) {
5546         /* input move, Shogi -> normal */
5547         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5548         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5549         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5550         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5551     } else
5552     if(move[1]=='@' &&
5553        move[3]>='0' && move[3]<='9' &&
5554        move[2]>='a' && move[2]<='x'    ) {
5555         move[1] = '*';
5556         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5557         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5558     } else
5559     if(
5560        move[0]>='a' && move[0]<='x' &&
5561        move[3]>='0' && move[3]<='9' &&
5562        move[2]>='a' && move[2]<='x'    ) {
5563          /* output move, normal -> Shogi */
5564         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5565         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5566         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5567         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5568         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5569     }
5570     if (appData.debugMode) {
5571         fprintf(debugFP, "   out = '%s'\n", move);
5572     }
5573 }
5574
5575 char yy_textstr[8000];
5576
5577 /* Parser for moves from gnuchess, ICS, or user typein box */
5578 Boolean
5579 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5580 {
5581     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5582
5583     switch (*moveType) {
5584       case WhitePromotion:
5585       case BlackPromotion:
5586       case WhiteNonPromotion:
5587       case BlackNonPromotion:
5588       case NormalMove:
5589       case FirstLeg:
5590       case WhiteCapturesEnPassant:
5591       case BlackCapturesEnPassant:
5592       case WhiteKingSideCastle:
5593       case WhiteQueenSideCastle:
5594       case BlackKingSideCastle:
5595       case BlackQueenSideCastle:
5596       case WhiteKingSideCastleWild:
5597       case WhiteQueenSideCastleWild:
5598       case BlackKingSideCastleWild:
5599       case BlackQueenSideCastleWild:
5600       /* Code added by Tord: */
5601       case WhiteHSideCastleFR:
5602       case WhiteASideCastleFR:
5603       case BlackHSideCastleFR:
5604       case BlackASideCastleFR:
5605       /* End of code added by Tord */
5606       case IllegalMove:         /* bug or odd chess variant */
5607         if(currentMoveString[1] == '@') { // illegal drop
5608           *fromX = WhiteOnMove(moveNum) ?
5609             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5610             (int) CharToPiece(ToLower(currentMoveString[0]));
5611           goto drop;
5612         }
5613         *fromX = currentMoveString[0] - AAA;
5614         *fromY = currentMoveString[1] - ONE;
5615         *toX = currentMoveString[2] - AAA;
5616         *toY = currentMoveString[3] - ONE;
5617         *promoChar = currentMoveString[4];
5618         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5619         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5620             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5621     if (appData.debugMode) {
5622         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5623     }
5624             *fromX = *fromY = *toX = *toY = 0;
5625             return FALSE;
5626         }
5627         if (appData.testLegality) {
5628           return (*moveType != IllegalMove);
5629         } else {
5630           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5631                          // [HGM] lion: if this is a double move we are less critical
5632                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5633         }
5634
5635       case WhiteDrop:
5636       case BlackDrop:
5637         *fromX = *moveType == WhiteDrop ?
5638           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5639           (int) CharToPiece(ToLower(currentMoveString[0]));
5640       drop:
5641         *fromY = DROP_RANK;
5642         *toX = currentMoveString[2] - AAA;
5643         *toY = currentMoveString[3] - ONE;
5644         *promoChar = NULLCHAR;
5645         return TRUE;
5646
5647       case AmbiguousMove:
5648       case ImpossibleMove:
5649       case EndOfFile:
5650       case ElapsedTime:
5651       case Comment:
5652       case PGNTag:
5653       case NAG:
5654       case WhiteWins:
5655       case BlackWins:
5656       case GameIsDrawn:
5657       default:
5658     if (appData.debugMode) {
5659         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5660     }
5661         /* bug? */
5662         *fromX = *fromY = *toX = *toY = 0;
5663         *promoChar = NULLCHAR;
5664         return FALSE;
5665     }
5666 }
5667
5668 Boolean pushed = FALSE;
5669 char *lastParseAttempt;
5670
5671 void
5672 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5673 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5674   int fromX, fromY, toX, toY; char promoChar;
5675   ChessMove moveType;
5676   Boolean valid;
5677   int nr = 0;
5678
5679   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5680   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5681     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5682     pushed = TRUE;
5683   }
5684   endPV = forwardMostMove;
5685   do {
5686     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5687     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5688     lastParseAttempt = pv;
5689     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5690     if(!valid && nr == 0 &&
5691        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5692         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5693         // Hande case where played move is different from leading PV move
5694         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5695         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5696         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5697         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5698           endPV += 2; // if position different, keep this
5699           moveList[endPV-1][0] = fromX + AAA;
5700           moveList[endPV-1][1] = fromY + ONE;
5701           moveList[endPV-1][2] = toX + AAA;
5702           moveList[endPV-1][3] = toY + ONE;
5703           parseList[endPV-1][0] = NULLCHAR;
5704           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5705         }
5706       }
5707     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5708     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5709     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5710     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5711         valid++; // allow comments in PV
5712         continue;
5713     }
5714     nr++;
5715     if(endPV+1 > framePtr) break; // no space, truncate
5716     if(!valid) break;
5717     endPV++;
5718     CopyBoard(boards[endPV], boards[endPV-1]);
5719     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5720     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5721     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5722     CoordsToAlgebraic(boards[endPV - 1],
5723                              PosFlags(endPV - 1),
5724                              fromY, fromX, toY, toX, promoChar,
5725                              parseList[endPV - 1]);
5726   } while(valid);
5727   if(atEnd == 2) return; // used hidden, for PV conversion
5728   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5729   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5730   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5731                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5732   DrawPosition(TRUE, boards[currentMove]);
5733 }
5734
5735 int
5736 MultiPV (ChessProgramState *cps, int kind)
5737 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5738         int i;
5739         for(i=0; i<cps->nrOptions; i++) {
5740             char *s = cps->option[i].name;
5741             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5742             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5743                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5744         }
5745         return -1;
5746 }
5747
5748 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5749 static int multi, pv_margin;
5750 static ChessProgramState *activeCps;
5751
5752 Boolean
5753 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5754 {
5755         int startPV, lineStart, origIndex = index;
5756         char *p, buf2[MSG_SIZ];
5757         ChessProgramState *cps = (pane ? &second : &first);
5758
5759         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5760         lastX = x; lastY = y;
5761         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5762         lineStart = startPV = index;
5763         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5764         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5765         index = startPV;
5766         do{ while(buf[index] && buf[index] != '\n') index++;
5767         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5768         buf[index] = 0;
5769         if(lineStart == 0 && gameMode == AnalyzeMode) {
5770             int n = 0;
5771             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5772             if(n == 0) { // click not on "fewer" or "more"
5773                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5774                     pv_margin = cps->option[multi].value;
5775                     activeCps = cps; // non-null signals margin adjustment
5776                 }
5777             } else if((multi = MultiPV(cps, 1)) >= 0) {
5778                 n += cps->option[multi].value; if(n < 1) n = 1;
5779                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5780                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5781                 cps->option[multi].value = n;
5782                 *start = *end = 0;
5783                 return FALSE;
5784             }
5785         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5786                 ExcludeClick(origIndex - lineStart);
5787                 return FALSE;
5788         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5789                 Collapse(origIndex - lineStart);
5790                 return FALSE;
5791         }
5792         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5793         *start = startPV; *end = index-1;
5794         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5795         return TRUE;
5796 }
5797
5798 char *
5799 PvToSAN (char *pv)
5800 {
5801         static char buf[10*MSG_SIZ];
5802         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5803         *buf = NULLCHAR;
5804         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5805         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5806         for(i = forwardMostMove; i<endPV; i++){
5807             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5808             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5809             k += strlen(buf+k);
5810         }
5811         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5812         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5813         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5814         endPV = savedEnd;
5815         return buf;
5816 }
5817
5818 Boolean
5819 LoadPV (int x, int y)
5820 { // called on right mouse click to load PV
5821   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5822   lastX = x; lastY = y;
5823   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5824   extendGame = FALSE;
5825   return TRUE;
5826 }
5827
5828 void
5829 UnLoadPV ()
5830 {
5831   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5832   if(activeCps) {
5833     if(pv_margin != activeCps->option[multi].value) {
5834       char buf[MSG_SIZ];
5835       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5836       SendToProgram(buf, activeCps);
5837       activeCps->option[multi].value = pv_margin;
5838     }
5839     activeCps = NULL;
5840     return;
5841   }
5842   if(endPV < 0) return;
5843   if(appData.autoCopyPV) CopyFENToClipboard();
5844   endPV = -1;
5845   if(extendGame && currentMove > forwardMostMove) {
5846         Boolean saveAnimate = appData.animate;
5847         if(pushed) {
5848             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5849                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5850             } else storedGames--; // abandon shelved tail of original game
5851         }
5852         pushed = FALSE;
5853         forwardMostMove = currentMove;
5854         currentMove = oldFMM;
5855         appData.animate = FALSE;
5856         ToNrEvent(forwardMostMove);
5857         appData.animate = saveAnimate;
5858   }
5859   currentMove = forwardMostMove;
5860   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5861   ClearPremoveHighlights();
5862   DrawPosition(TRUE, boards[currentMove]);
5863 }
5864
5865 void
5866 MovePV (int x, int y, int h)
5867 { // step through PV based on mouse coordinates (called on mouse move)
5868   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5869
5870   if(activeCps) { // adjusting engine's multi-pv margin
5871     if(x > lastX) pv_margin++; else
5872     if(x < lastX) pv_margin -= (pv_margin > 0);
5873     if(x != lastX) {
5874       char buf[MSG_SIZ];
5875       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5876       DisplayMessage(buf, "");
5877     }
5878     lastX = x;
5879     return;
5880   }
5881   // we must somehow check if right button is still down (might be released off board!)
5882   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5883   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5884   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5885   if(!step) return;
5886   lastX = x; lastY = y;
5887
5888   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5889   if(endPV < 0) return;
5890   if(y < margin) step = 1; else
5891   if(y > h - margin) step = -1;
5892   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5893   currentMove += step;
5894   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5895   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5896                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5897   DrawPosition(FALSE, boards[currentMove]);
5898 }
5899
5900
5901 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5902 // All positions will have equal probability, but the current method will not provide a unique
5903 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5904 #define DARK 1
5905 #define LITE 2
5906 #define ANY 3
5907
5908 int squaresLeft[4];
5909 int piecesLeft[(int)BlackPawn];
5910 int seed, nrOfShuffles;
5911
5912 void
5913 GetPositionNumber ()
5914 {       // sets global variable seed
5915         int i;
5916
5917         seed = appData.defaultFrcPosition;
5918         if(seed < 0) { // randomize based on time for negative FRC position numbers
5919                 for(i=0; i<50; i++) seed += random();
5920                 seed = random() ^ random() >> 8 ^ random() << 8;
5921                 if(seed<0) seed = -seed;
5922         }
5923 }
5924
5925 int
5926 put (Board board, int pieceType, int rank, int n, int shade)
5927 // put the piece on the (n-1)-th empty squares of the given shade
5928 {
5929         int i;
5930
5931         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5932                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5933                         board[rank][i] = (ChessSquare) pieceType;
5934                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5935                         squaresLeft[ANY]--;
5936                         piecesLeft[pieceType]--;
5937                         return i;
5938                 }
5939         }
5940         return -1;
5941 }
5942
5943
5944 void
5945 AddOnePiece (Board board, int pieceType, int rank, int shade)
5946 // calculate where the next piece goes, (any empty square), and put it there
5947 {
5948         int i;
5949
5950         i = seed % squaresLeft[shade];
5951         nrOfShuffles *= squaresLeft[shade];
5952         seed /= squaresLeft[shade];
5953         put(board, pieceType, rank, i, shade);
5954 }
5955
5956 void
5957 AddTwoPieces (Board board, int pieceType, int rank)
5958 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5959 {
5960         int i, n=squaresLeft[ANY], j=n-1, k;
5961
5962         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5963         i = seed % k;  // pick one
5964         nrOfShuffles *= k;
5965         seed /= k;
5966         while(i >= j) i -= j--;
5967         j = n - 1 - j; i += j;
5968         put(board, pieceType, rank, j, ANY);
5969         put(board, pieceType, rank, i, ANY);
5970 }
5971
5972 void
5973 SetUpShuffle (Board board, int number)
5974 {
5975         int i, p, first=1;
5976
5977         GetPositionNumber(); nrOfShuffles = 1;
5978
5979         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5980         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5981         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5982
5983         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5984
5985         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5986             p = (int) board[0][i];
5987             if(p < (int) BlackPawn) piecesLeft[p] ++;
5988             board[0][i] = EmptySquare;
5989         }
5990
5991         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5992             // shuffles restricted to allow normal castling put KRR first
5993             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5994                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5995             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5996                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5997             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5998                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5999             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
6000                 put(board, WhiteRook, 0, 0, ANY);
6001             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
6002         }
6003
6004         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
6005             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
6006             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
6007                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
6008                 while(piecesLeft[p] >= 2) {
6009                     AddOnePiece(board, p, 0, LITE);
6010                     AddOnePiece(board, p, 0, DARK);
6011                 }
6012                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6013             }
6014
6015         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6016             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6017             // but we leave King and Rooks for last, to possibly obey FRC restriction
6018             if(p == (int)WhiteRook) continue;
6019             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6020             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
6021         }
6022
6023         // now everything is placed, except perhaps King (Unicorn) and Rooks
6024
6025         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6026             // Last King gets castling rights
6027             while(piecesLeft[(int)WhiteUnicorn]) {
6028                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6029                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6030             }
6031
6032             while(piecesLeft[(int)WhiteKing]) {
6033                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6034                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6035             }
6036
6037
6038         } else {
6039             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6040             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6041         }
6042
6043         // Only Rooks can be left; simply place them all
6044         while(piecesLeft[(int)WhiteRook]) {
6045                 i = put(board, WhiteRook, 0, 0, ANY);
6046                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6047                         if(first) {
6048                                 first=0;
6049                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6050                         }
6051                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6052                 }
6053         }
6054         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6055             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6056         }
6057
6058         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6059 }
6060
6061 int
6062 ptclen (const char *s, char *escapes)
6063 {
6064     int n = 0;
6065     if(!*escapes) return strlen(s);
6066     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6067     return n;
6068 }
6069
6070 int
6071 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6072 /* [HGM] moved here from winboard.c because of its general usefulness */
6073 /*       Basically a safe strcpy that uses the last character as King */
6074 {
6075     int result = FALSE; int NrPieces;
6076     unsigned char partner[EmptySquare];
6077
6078     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6079                     && NrPieces >= 12 && !(NrPieces&1)) {
6080         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6081
6082         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6083         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6084             char *p, c=0;
6085             if(map[j] == '/') offs = WhitePBishop - i, j++;
6086             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6087             table[i+offs] = map[j++];
6088             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6089             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6090             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6091         }
6092         table[(int) WhiteKing]  = map[j++];
6093         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6094             char *p, c=0;
6095             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6096             i = WHITE_TO_BLACK ii;
6097             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6098             table[i+offs] = map[j++];
6099             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6100             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6101             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6102         }
6103         table[(int) BlackKing]  = map[j++];
6104
6105
6106         if(*escapes) { // set up promotion pairing
6107             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6108             // pieceToChar entirely filled, so we can look up specified partners
6109             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6110                 int c = table[i];
6111                 if(c == '^' || c == '-') { // has specified partner
6112                     int p;
6113                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6114                     if(c == '^') table[i] = '+';
6115                     if(p < EmptySquare) {
6116                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6117                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6118                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6119                     }
6120                 } else if(c == '*') {
6121                     table[i] = partner[i];
6122                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6123                 }
6124             }
6125         }
6126
6127         result = TRUE;
6128     }
6129
6130     return result;
6131 }
6132
6133 int
6134 SetCharTable (unsigned char *table, const char * map)
6135 {
6136     return SetCharTableEsc(table, map, "");
6137 }
6138
6139 void
6140 Prelude (Board board)
6141 {       // [HGM] superchess: random selection of exo-pieces
6142         int i, j, k; ChessSquare p;
6143         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6144
6145         GetPositionNumber(); // use FRC position number
6146
6147         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6148             SetCharTable(pieceToChar, appData.pieceToCharTable);
6149             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6150                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6151         }
6152
6153         j = seed%4;                 seed /= 4;
6154         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6155         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6156         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6157         j = seed%3 + (seed%3 >= j); seed /= 3;
6158         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6159         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6160         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6161         j = seed%3;                 seed /= 3;
6162         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6163         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6164         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6165         j = seed%2 + (seed%2 >= j); seed /= 2;
6166         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6167         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6168         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6169         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6170         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6171         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6172         put(board, exoPieces[0],    0, 0, ANY);
6173         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6174 }
6175
6176 void
6177 InitPosition (int redraw)
6178 {
6179     ChessSquare (* pieces)[BOARD_FILES];
6180     int i, j, pawnRow=1, pieceRows=1, overrule,
6181     oldx = gameInfo.boardWidth,
6182     oldy = gameInfo.boardHeight,
6183     oldh = gameInfo.holdingsWidth;
6184     static int oldv;
6185
6186     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6187
6188     /* [AS] Initialize pv info list [HGM] and game status */
6189     {
6190         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6191             pvInfoList[i].depth = 0;
6192             boards[i][EP_STATUS] = EP_NONE;
6193             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6194         }
6195
6196         initialRulePlies = 0; /* 50-move counter start */
6197
6198         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6199         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6200     }
6201
6202
6203     /* [HGM] logic here is completely changed. In stead of full positions */
6204     /* the initialized data only consist of the two backranks. The switch */
6205     /* selects which one we will use, which is than copied to the Board   */
6206     /* initialPosition, which for the rest is initialized by Pawns and    */
6207     /* empty squares. This initial position is then copied to boards[0],  */
6208     /* possibly after shuffling, so that it remains available.            */
6209
6210     gameInfo.holdingsWidth = 0; /* default board sizes */
6211     gameInfo.boardWidth    = 8;
6212     gameInfo.boardHeight   = 8;
6213     gameInfo.holdingsSize  = 0;
6214     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6215     for(i=0; i<BOARD_FILES-6; i++)
6216       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6217     initialPosition[EP_STATUS] = EP_NONE;
6218     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6219     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6220     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6221          SetCharTable(pieceNickName, appData.pieceNickNames);
6222     else SetCharTable(pieceNickName, "............");
6223     pieces = FIDEArray;
6224
6225     switch (gameInfo.variant) {
6226     case VariantFischeRandom:
6227       shuffleOpenings = TRUE;
6228       appData.fischerCastling = TRUE;
6229     default:
6230       break;
6231     case VariantShatranj:
6232       pieces = ShatranjArray;
6233       nrCastlingRights = 0;
6234       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6235       break;
6236     case VariantMakruk:
6237       pieces = makrukArray;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6240       break;
6241     case VariantASEAN:
6242       pieces = aseanArray;
6243       nrCastlingRights = 0;
6244       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6245       break;
6246     case VariantTwoKings:
6247       pieces = twoKingsArray;
6248       break;
6249     case VariantGrand:
6250       pieces = GrandArray;
6251       nrCastlingRights = 0;
6252       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6253       gameInfo.boardWidth = 10;
6254       gameInfo.boardHeight = 10;
6255       gameInfo.holdingsSize = 7;
6256       break;
6257     case VariantCapaRandom:
6258       shuffleOpenings = TRUE;
6259       appData.fischerCastling = TRUE;
6260     case VariantCapablanca:
6261       pieces = CapablancaArray;
6262       gameInfo.boardWidth = 10;
6263       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6264       break;
6265     case VariantGothic:
6266       pieces = GothicArray;
6267       gameInfo.boardWidth = 10;
6268       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6269       break;
6270     case VariantSChess:
6271       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6272       gameInfo.holdingsSize = 7;
6273       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6274       break;
6275     case VariantJanus:
6276       pieces = JanusArray;
6277       gameInfo.boardWidth = 10;
6278       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6279       nrCastlingRights = 6;
6280         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6281         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6282         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6283         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6284         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6285         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6286       break;
6287     case VariantFalcon:
6288       pieces = FalconArray;
6289       gameInfo.boardWidth = 10;
6290       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6291       break;
6292     case VariantXiangqi:
6293       pieces = XiangqiArray;
6294       gameInfo.boardWidth  = 9;
6295       gameInfo.boardHeight = 10;
6296       nrCastlingRights = 0;
6297       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6298       break;
6299     case VariantShogi:
6300       pieces = ShogiArray;
6301       gameInfo.boardWidth  = 9;
6302       gameInfo.boardHeight = 9;
6303       gameInfo.holdingsSize = 7;
6304       nrCastlingRights = 0;
6305       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6306       break;
6307     case VariantChu:
6308       pieces = ChuArray; pieceRows = 3;
6309       gameInfo.boardWidth  = 12;
6310       gameInfo.boardHeight = 12;
6311       nrCastlingRights = 0;
6312 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6313   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6314       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6315                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6316       break;
6317     case VariantCourier:
6318       pieces = CourierArray;
6319       gameInfo.boardWidth  = 12;
6320       nrCastlingRights = 0;
6321       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6322       break;
6323     case VariantKnightmate:
6324       pieces = KnightmateArray;
6325       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6326       break;
6327     case VariantSpartan:
6328       pieces = SpartanArray;
6329       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6330       break;
6331     case VariantLion:
6332       pieces = lionArray;
6333       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6334       break;
6335     case VariantChuChess:
6336       pieces = ChuChessArray;
6337       gameInfo.boardWidth = 10;
6338       gameInfo.boardHeight = 10;
6339       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6340       break;
6341     case VariantFairy:
6342       pieces = fairyArray;
6343       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6344       break;
6345     case VariantGreat:
6346       pieces = GreatArray;
6347       gameInfo.boardWidth = 10;
6348       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6349       gameInfo.holdingsSize = 8;
6350       break;
6351     case VariantSuper:
6352       pieces = FIDEArray;
6353       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6354       gameInfo.holdingsSize = 8;
6355       startedFromSetupPosition = TRUE;
6356       break;
6357     case VariantCrazyhouse:
6358     case VariantBughouse:
6359       pieces = FIDEArray;
6360       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6361       gameInfo.holdingsSize = 5;
6362       break;
6363     case VariantWildCastle:
6364       pieces = FIDEArray;
6365       /* !!?shuffle with kings guaranteed to be on d or e file */
6366       shuffleOpenings = 1;
6367       break;
6368     case VariantNoCastle:
6369       /* !!?unconstrained back-rank shuffle */
6370       shuffleOpenings = 1;
6371     case VariantSuicide:
6372       pieces = FIDEArray;
6373       nrCastlingRights = 0;
6374       break;
6375     }
6376
6377     overrule = 0;
6378     if(appData.NrFiles >= 0) {
6379         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6380         gameInfo.boardWidth = appData.NrFiles;
6381     }
6382     if(appData.NrRanks >= 0) {
6383         gameInfo.boardHeight = appData.NrRanks;
6384     }
6385     if(appData.holdingsSize >= 0) {
6386         i = appData.holdingsSize;
6387 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6388         gameInfo.holdingsSize = i;
6389     }
6390     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6391     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6392         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6393
6394     if(!handSize) handSize = BOARD_HEIGHT;
6395     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6396     if(pawnRow < 1) pawnRow = 1;
6397     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6398        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6399     if(gameInfo.variant == VariantChu) pawnRow = 3;
6400
6401     /* User pieceToChar list overrules defaults */
6402     if(appData.pieceToCharTable != NULL)
6403         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6404
6405     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6406
6407         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6408             s = (ChessSquare) 0; /* account holding counts in guard band */
6409         for( i=0; i<BOARD_HEIGHT; i++ )
6410             initialPosition[i][j] = s;
6411
6412         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6413         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6414         initialPosition[pawnRow][j] = WhitePawn;
6415         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6416         if(gameInfo.variant == VariantXiangqi) {
6417             if(j&1) {
6418                 initialPosition[pawnRow][j] =
6419                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6420                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6421                    initialPosition[2][j] = WhiteCannon;
6422                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6423                 }
6424             }
6425         }
6426         if(gameInfo.variant == VariantChu) {
6427              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6428                initialPosition[pawnRow+1][j] = WhiteCobra,
6429                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6430              for(i=1; i<pieceRows; i++) {
6431                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6432                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6433              }
6434         }
6435         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6436             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6437                initialPosition[0][j] = WhiteRook;
6438                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6439             }
6440         }
6441         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6442     }
6443     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6444     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6445
6446             j=BOARD_LEFT+1;
6447             initialPosition[1][j] = WhiteBishop;
6448             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6449             j=BOARD_RGHT-2;
6450             initialPosition[1][j] = WhiteRook;
6451             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6452     }
6453
6454     if( nrCastlingRights == -1) {
6455         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6456         /*       This sets default castling rights from none to normal corners   */
6457         /* Variants with other castling rights must set them themselves above    */
6458         nrCastlingRights = 6;
6459
6460         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6461         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6462         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6463         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6464         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6465         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6466      }
6467
6468      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6469      if(gameInfo.variant == VariantGreat) { // promotion commoners
6470         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6471         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6472         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6473         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6474      }
6475      if( gameInfo.variant == VariantSChess ) {
6476       initialPosition[1][0] = BlackMarshall;
6477       initialPosition[2][0] = BlackAngel;
6478       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6479       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6480       initialPosition[1][1] = initialPosition[2][1] =
6481       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6482      }
6483      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6484   if (appData.debugMode) {
6485     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6486   }
6487     if(shuffleOpenings) {
6488         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6489         startedFromSetupPosition = TRUE;
6490     }
6491     if(startedFromPositionFile) {
6492       /* [HGM] loadPos: use PositionFile for every new game */
6493       CopyBoard(initialPosition, filePosition);
6494       for(i=0; i<nrCastlingRights; i++)
6495           initialRights[i] = filePosition[CASTLING][i];
6496       startedFromSetupPosition = TRUE;
6497     }
6498     if(*appData.men) LoadPieceDesc(appData.men);
6499
6500     CopyBoard(boards[0], initialPosition);
6501
6502     if(oldx != gameInfo.boardWidth ||
6503        oldy != gameInfo.boardHeight ||
6504        oldv != gameInfo.variant ||
6505        oldh != gameInfo.holdingsWidth
6506                                          )
6507             InitDrawingSizes(-2 ,0);
6508
6509     oldv = gameInfo.variant;
6510     if (redraw)
6511       DrawPosition(TRUE, boards[currentMove]);
6512 }
6513
6514 void
6515 SendBoard (ChessProgramState *cps, int moveNum)
6516 {
6517     char message[MSG_SIZ];
6518
6519     if (cps->useSetboard) {
6520       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6521       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6522       SendToProgram(message, cps);
6523       free(fen);
6524
6525     } else {
6526       ChessSquare *bp;
6527       int i, j, left=0, right=BOARD_WIDTH;
6528       /* Kludge to set black to move, avoiding the troublesome and now
6529        * deprecated "black" command.
6530        */
6531       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6532         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6533
6534       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6535
6536       SendToProgram("edit\n", cps);
6537       SendToProgram("#\n", cps);
6538       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6539         bp = &boards[moveNum][i][left];
6540         for (j = left; j < right; j++, bp++) {
6541           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6542           if ((int) *bp < (int) BlackPawn) {
6543             if(j == BOARD_RGHT+1)
6544                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6545             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6546             if(message[0] == '+' || message[0] == '~') {
6547               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6548                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6549                         AAA + j, ONE + i - '0');
6550             }
6551             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6552                 message[1] = BOARD_RGHT   - 1 - j + '1';
6553                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6554             }
6555             SendToProgram(message, cps);
6556           }
6557         }
6558       }
6559
6560       SendToProgram("c\n", cps);
6561       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6562         bp = &boards[moveNum][i][left];
6563         for (j = left; j < right; j++, bp++) {
6564           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6565           if (((int) *bp != (int) EmptySquare)
6566               && ((int) *bp >= (int) BlackPawn)) {
6567             if(j == BOARD_LEFT-2)
6568                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6569             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6570                     AAA + j, ONE + i - '0');
6571             if(message[0] == '+' || message[0] == '~') {
6572               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6573                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6574                         AAA + j, ONE + i - '0');
6575             }
6576             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6577                 message[1] = BOARD_RGHT   - 1 - j + '1';
6578                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6579             }
6580             SendToProgram(message, cps);
6581           }
6582         }
6583       }
6584
6585       SendToProgram(".\n", cps);
6586     }
6587     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6588 }
6589
6590 char exclusionHeader[MSG_SIZ];
6591 int exCnt, excludePtr;
6592 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6593 static Exclusion excluTab[200];
6594 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6595
6596 static void
6597 WriteMap (int s)
6598 {
6599     int j;
6600     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6601     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6602 }
6603
6604 static void
6605 ClearMap ()
6606 {
6607     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6608     excludePtr = 24; exCnt = 0;
6609     WriteMap(0);
6610 }
6611
6612 static void
6613 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6614 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6615     char buf[2*MOVE_LEN], *p;
6616     Exclusion *e = excluTab;
6617     int i;
6618     for(i=0; i<exCnt; i++)
6619         if(e[i].ff == fromX && e[i].fr == fromY &&
6620            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6621     if(i == exCnt) { // was not in exclude list; add it
6622         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6623         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6624             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6625             return; // abort
6626         }
6627         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6628         excludePtr++; e[i].mark = excludePtr++;
6629         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6630         exCnt++;
6631     }
6632     exclusionHeader[e[i].mark] = state;
6633 }
6634
6635 static int
6636 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6637 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6638     char buf[MSG_SIZ];
6639     int j, k;
6640     ChessMove moveType;
6641     if((signed char)promoChar == -1) { // kludge to indicate best move
6642         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6643             return 1; // if unparsable, abort
6644     }
6645     // update exclusion map (resolving toggle by consulting existing state)
6646     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6647     j = k%8; k >>= 3;
6648     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6649     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6650          excludeMap[k] |=   1<<j;
6651     else excludeMap[k] &= ~(1<<j);
6652     // update header
6653     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6654     // inform engine
6655     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6656     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6657     SendToBoth(buf);
6658     return (state == '+');
6659 }
6660
6661 static void
6662 ExcludeClick (int index)
6663 {
6664     int i, j;
6665     Exclusion *e = excluTab;
6666     if(index < 25) { // none, best or tail clicked
6667         if(index < 13) { // none: include all
6668             WriteMap(0); // clear map
6669             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6670             SendToBoth("include all\n"); // and inform engine
6671         } else if(index > 18) { // tail
6672             if(exclusionHeader[19] == '-') { // tail was excluded
6673                 SendToBoth("include all\n");
6674                 WriteMap(0); // clear map completely
6675                 // now re-exclude selected moves
6676                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6677                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6678             } else { // tail was included or in mixed state
6679                 SendToBoth("exclude all\n");
6680                 WriteMap(0xFF); // fill map completely
6681                 // now re-include selected moves
6682                 j = 0; // count them
6683                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6684                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6685                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6686             }
6687         } else { // best
6688             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6689         }
6690     } else {
6691         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6692             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6693             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6694             break;
6695         }
6696     }
6697 }
6698
6699 ChessSquare
6700 DefaultPromoChoice (int white)
6701 {
6702     ChessSquare result;
6703     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6704        gameInfo.variant == VariantMakruk)
6705         result = WhiteFerz; // no choice
6706     else if(gameInfo.variant == VariantASEAN)
6707         result = WhiteRook; // no choice
6708     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6709         result= WhiteKing; // in Suicide Q is the last thing we want
6710     else if(gameInfo.variant == VariantSpartan)
6711         result = white ? WhiteQueen : WhiteAngel;
6712     else result = WhiteQueen;
6713     if(!white) result = WHITE_TO_BLACK result;
6714     return result;
6715 }
6716
6717 static int autoQueen; // [HGM] oneclick
6718
6719 int
6720 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6721 {
6722     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6723     /* [HGM] add Shogi promotions */
6724     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6725     ChessSquare piece, partner;
6726     ChessMove moveType;
6727     Boolean premove;
6728
6729     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6730     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6731
6732     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6733       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6734         return FALSE;
6735
6736     if(legal[toY][toX] == 4) return FALSE;
6737
6738     piece = boards[currentMove][fromY][fromX];
6739     if(gameInfo.variant == VariantChu) {
6740         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6741         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6742         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6743     } else if(gameInfo.variant == VariantShogi) {
6744         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6745         highestPromotingPiece = (int)WhiteAlfil;
6746         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6747     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6748         promotionZoneSize = 3;
6749     }
6750
6751     // Treat Lance as Pawn when it is not representing Amazon or Lance
6752     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6753         if(piece == WhiteLance) piece = WhitePawn; else
6754         if(piece == BlackLance) piece = BlackPawn;
6755     }
6756
6757     // next weed out all moves that do not touch the promotion zone at all
6758     if((int)piece >= BlackPawn) {
6759         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6760              return FALSE;
6761         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6762         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6763     } else {
6764         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6765            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6766         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6767              return FALSE;
6768     }
6769
6770     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6771
6772     // weed out mandatory Shogi promotions
6773     if(gameInfo.variant == VariantShogi) {
6774         if(piece >= BlackPawn) {
6775             if(toY == 0 && piece == BlackPawn ||
6776                toY == 0 && piece == BlackQueen ||
6777                toY <= 1 && piece == BlackKnight) {
6778                 *promoChoice = '+';
6779                 return FALSE;
6780             }
6781         } else {
6782             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6783                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6784                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6785                 *promoChoice = '+';
6786                 return FALSE;
6787             }
6788         }
6789     }
6790
6791     // weed out obviously illegal Pawn moves
6792     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6793         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6794         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6795         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6796         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6797         // note we are not allowed to test for valid (non-)capture, due to premove
6798     }
6799
6800     // we either have a choice what to promote to, or (in Shogi) whether to promote
6801     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6802        gameInfo.variant == VariantMakruk) {
6803         ChessSquare p=BlackFerz;  // no choice
6804         while(p < EmptySquare) {  //but make sure we use piece that exists
6805             *promoChoice = PieceToChar(p++);
6806             if(*promoChoice != '.') break;
6807         }
6808         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6809     }
6810     // no sense asking what we must promote to if it is going to explode...
6811     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6812         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6813         return FALSE;
6814     }
6815     // give caller the default choice even if we will not make it
6816     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6817     partner = piece; // pieces can promote if the pieceToCharTable says so
6818     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6819     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6820     if(        sweepSelect && gameInfo.variant != VariantGreat
6821                            && gameInfo.variant != VariantGrand
6822                            && gameInfo.variant != VariantSuper) return FALSE;
6823     if(autoQueen) return FALSE; // predetermined
6824
6825     // suppress promotion popup on illegal moves that are not premoves
6826     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6827               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6828     if(appData.testLegality && !premove) {
6829         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6830                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6831         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6832         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6833             return FALSE;
6834     }
6835
6836     return TRUE;
6837 }
6838
6839 int
6840 InPalace (int row, int column)
6841 {   /* [HGM] for Xiangqi */
6842     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6843          column < (BOARD_WIDTH + 4)/2 &&
6844          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6845     return FALSE;
6846 }
6847
6848 int
6849 PieceForSquare (int x, int y)
6850 {
6851   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6852   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6853   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6854      return boards[currentMove][y][x];
6855 }
6856
6857 ChessSquare
6858 More (Board board, int col, int start, int end)
6859 {
6860     int k;
6861     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6862     return EmptySquare;
6863 }
6864
6865 void
6866 DrawPosition (int repaint, Board board)
6867 {
6868     Board compactedBoard;
6869     if(handSize > BOARD_HEIGHT && board) {
6870         int k;
6871         CopyBoard(compactedBoard, board);
6872         if(handOffsets & 1) {
6873             for(k=0; k<BOARD_HEIGHT; k++) {
6874                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6875                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6876             }
6877             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6878         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6879         if(!(handOffsets & 2)) {
6880             for(k=0; k<BOARD_HEIGHT; k++) {
6881                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6882                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6883             }
6884             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6885         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6886         DrawPositionX(TRUE, compactedBoard);
6887     } else DrawPositionX(repaint, board);
6888 }
6889
6890 int
6891 OKToStartUserMove (int x, int y)
6892 {
6893     ChessSquare from_piece;
6894     int white_piece;
6895
6896     if (matchMode) return FALSE;
6897     if (gameMode == EditPosition) return TRUE;
6898
6899     if (x >= 0 && y >= 0)
6900       from_piece = boards[currentMove][y][x];
6901     else
6902       from_piece = EmptySquare;
6903
6904     if (from_piece == EmptySquare) return FALSE;
6905
6906     white_piece = (int)from_piece >= (int)WhitePawn &&
6907       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6908
6909     switch (gameMode) {
6910       case AnalyzeFile:
6911       case TwoMachinesPlay:
6912       case EndOfGame:
6913         return FALSE;
6914
6915       case IcsObserving:
6916       case IcsIdle:
6917         return FALSE;
6918
6919       case MachinePlaysWhite:
6920       case IcsPlayingBlack:
6921         if (appData.zippyPlay) return FALSE;
6922         if (white_piece) {
6923             DisplayMoveError(_("You are playing Black"));
6924             return FALSE;
6925         }
6926         break;
6927
6928       case MachinePlaysBlack:
6929       case IcsPlayingWhite:
6930         if (appData.zippyPlay) return FALSE;
6931         if (!white_piece) {
6932             DisplayMoveError(_("You are playing White"));
6933             return FALSE;
6934         }
6935         break;
6936
6937       case PlayFromGameFile:
6938             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6939       case EditGame:
6940       case AnalyzeMode:
6941         if (!white_piece && WhiteOnMove(currentMove)) {
6942             DisplayMoveError(_("It is White's turn"));
6943             return FALSE;
6944         }
6945         if (white_piece && !WhiteOnMove(currentMove)) {
6946             DisplayMoveError(_("It is Black's turn"));
6947             return FALSE;
6948         }
6949         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6950             /* Editing correspondence game history */
6951             /* Could disallow this or prompt for confirmation */
6952             cmailOldMove = -1;
6953         }
6954         break;
6955
6956       case BeginningOfGame:
6957         if (appData.icsActive) return FALSE;
6958         if (!appData.noChessProgram) {
6959             if (!white_piece) {
6960                 DisplayMoveError(_("You are playing White"));
6961                 return FALSE;
6962             }
6963         }
6964         break;
6965
6966       case Training:
6967         if (!white_piece && WhiteOnMove(currentMove)) {
6968             DisplayMoveError(_("It is White's turn"));
6969             return FALSE;
6970         }
6971         if (white_piece && !WhiteOnMove(currentMove)) {
6972             DisplayMoveError(_("It is Black's turn"));
6973             return FALSE;
6974         }
6975         break;
6976
6977       default:
6978       case IcsExamining:
6979         break;
6980     }
6981     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6982         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6983         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6984         && gameMode != AnalyzeFile && gameMode != Training) {
6985         DisplayMoveError(_("Displayed position is not current"));
6986         return FALSE;
6987     }
6988     return TRUE;
6989 }
6990
6991 Boolean
6992 OnlyMove (int *x, int *y, Boolean captures)
6993 {
6994     DisambiguateClosure cl;
6995     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6996     switch(gameMode) {
6997       case MachinePlaysBlack:
6998       case IcsPlayingWhite:
6999       case BeginningOfGame:
7000         if(!WhiteOnMove(currentMove)) return FALSE;
7001         break;
7002       case MachinePlaysWhite:
7003       case IcsPlayingBlack:
7004         if(WhiteOnMove(currentMove)) return FALSE;
7005         break;
7006       case EditGame:
7007         break;
7008       default:
7009         return FALSE;
7010     }
7011     cl.pieceIn = EmptySquare;
7012     cl.rfIn = *y;
7013     cl.ffIn = *x;
7014     cl.rtIn = -1;
7015     cl.ftIn = -1;
7016     cl.promoCharIn = NULLCHAR;
7017     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7018     if( cl.kind == NormalMove ||
7019         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7020         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7021         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7022       fromX = cl.ff;
7023       fromY = cl.rf;
7024       *x = cl.ft;
7025       *y = cl.rt;
7026       return TRUE;
7027     }
7028     if(cl.kind != ImpossibleMove) return FALSE;
7029     cl.pieceIn = EmptySquare;
7030     cl.rfIn = -1;
7031     cl.ffIn = -1;
7032     cl.rtIn = *y;
7033     cl.ftIn = *x;
7034     cl.promoCharIn = NULLCHAR;
7035     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7036     if( cl.kind == NormalMove ||
7037         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7038         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7039         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7040       fromX = cl.ff;
7041       fromY = cl.rf;
7042       *x = cl.ft;
7043       *y = cl.rt;
7044       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7045       return TRUE;
7046     }
7047     return FALSE;
7048 }
7049
7050 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7051 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7052 int lastLoadGameUseList = FALSE;
7053 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7054 ChessMove lastLoadGameStart = EndOfFile;
7055 int doubleClick;
7056 Boolean addToBookFlag;
7057 static Board rightsBoard, nullBoard;
7058
7059 void
7060 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7061 {
7062     ChessMove moveType;
7063     ChessSquare pup;
7064     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7065
7066     /* Check if the user is playing in turn.  This is complicated because we
7067        let the user "pick up" a piece before it is his turn.  So the piece he
7068        tried to pick up may have been captured by the time he puts it down!
7069        Therefore we use the color the user is supposed to be playing in this
7070        test, not the color of the piece that is currently on the starting
7071        square---except in EditGame mode, where the user is playing both
7072        sides; fortunately there the capture race can't happen.  (It can
7073        now happen in IcsExamining mode, but that's just too bad.  The user
7074        will get a somewhat confusing message in that case.)
7075        */
7076
7077     switch (gameMode) {
7078       case AnalyzeFile:
7079       case TwoMachinesPlay:
7080       case EndOfGame:
7081       case IcsObserving:
7082       case IcsIdle:
7083         /* We switched into a game mode where moves are not accepted,
7084            perhaps while the mouse button was down. */
7085         return;
7086
7087       case MachinePlaysWhite:
7088         /* User is moving for Black */
7089         if (WhiteOnMove(currentMove)) {
7090             DisplayMoveError(_("It is White's turn"));
7091             return;
7092         }
7093         break;
7094
7095       case MachinePlaysBlack:
7096         /* User is moving for White */
7097         if (!WhiteOnMove(currentMove)) {
7098             DisplayMoveError(_("It is Black's turn"));
7099             return;
7100         }
7101         break;
7102
7103       case PlayFromGameFile:
7104             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7105       case EditGame:
7106       case IcsExamining:
7107       case BeginningOfGame:
7108       case AnalyzeMode:
7109       case Training:
7110         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7111         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7112             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7113             /* User is moving for Black */
7114             if (WhiteOnMove(currentMove)) {
7115                 DisplayMoveError(_("It is White's turn"));
7116                 return;
7117             }
7118         } else {
7119             /* User is moving for White */
7120             if (!WhiteOnMove(currentMove)) {
7121                 DisplayMoveError(_("It is Black's turn"));
7122                 return;
7123             }
7124         }
7125         break;
7126
7127       case IcsPlayingBlack:
7128         /* User is moving for Black */
7129         if (WhiteOnMove(currentMove)) {
7130             if (!appData.premove) {
7131                 DisplayMoveError(_("It is White's turn"));
7132             } else if (toX >= 0 && toY >= 0) {
7133                 premoveToX = toX;
7134                 premoveToY = toY;
7135                 premoveFromX = fromX;
7136                 premoveFromY = fromY;
7137                 premovePromoChar = promoChar;
7138                 gotPremove = 1;
7139                 if (appData.debugMode)
7140                     fprintf(debugFP, "Got premove: fromX %d,"
7141                             "fromY %d, toX %d, toY %d\n",
7142                             fromX, fromY, toX, toY);
7143             }
7144             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7145             return;
7146         }
7147         break;
7148
7149       case IcsPlayingWhite:
7150         /* User is moving for White */
7151         if (!WhiteOnMove(currentMove)) {
7152             if (!appData.premove) {
7153                 DisplayMoveError(_("It is Black's turn"));
7154             } else if (toX >= 0 && toY >= 0) {
7155                 premoveToX = toX;
7156                 premoveToY = toY;
7157                 premoveFromX = fromX;
7158                 premoveFromY = fromY;
7159                 premovePromoChar = promoChar;
7160                 gotPremove = 1;
7161                 if (appData.debugMode)
7162                     fprintf(debugFP, "Got premove: fromX %d,"
7163                             "fromY %d, toX %d, toY %d\n",
7164                             fromX, fromY, toX, toY);
7165             }
7166             DrawPosition(TRUE, boards[currentMove]);
7167             return;
7168         }
7169         break;
7170
7171       default:
7172         break;
7173
7174       case EditPosition:
7175         /* EditPosition, empty square, or different color piece;
7176            click-click move is possible */
7177         if (toX == -2 || toY == -2) {
7178             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7179             DrawPosition(FALSE, boards[currentMove]);
7180             return;
7181         } else if (toX >= 0 && toY >= 0) {
7182             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7183                 ChessSquare p = boards[0][rf][ff];
7184                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7185                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7186                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7187                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7188                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7189                     gatingPiece = p;
7190                 }
7191             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7192             boards[0][toY][toX] = boards[0][fromY][fromX];
7193             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7194                 if(boards[0][fromY][0] != EmptySquare) {
7195                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7196                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7197                 }
7198             } else
7199             if(fromX == BOARD_RGHT+1) {
7200                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7201                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7202                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7203                 }
7204             } else
7205             boards[0][fromY][fromX] = gatingPiece;
7206             ClearHighlights();
7207             DrawPosition(FALSE, boards[currentMove]);
7208             return;
7209         }
7210         return;
7211     }
7212
7213     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7214     pup = boards[currentMove][toY][toX];
7215
7216     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7217     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7218          if( pup != EmptySquare ) return;
7219          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7220            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7221                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7222            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7223            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7224            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7225            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7226          fromY = DROP_RANK;
7227     }
7228
7229     /* [HGM] always test for legality, to get promotion info */
7230     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7231                                          fromY, fromX, toY, toX, promoChar);
7232
7233     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7234
7235     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7236
7237     /* [HGM] but possibly ignore an IllegalMove result */
7238     if (appData.testLegality) {
7239         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7240             DisplayMoveError(_("Illegal move"));
7241             return;
7242         }
7243     }
7244
7245     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7246         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7247              ClearPremoveHighlights(); // was included
7248         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7249         DrawPosition(FALSE, NULL);
7250         return;
7251     }
7252
7253     if(addToBookFlag) { // adding moves to book
7254         char buf[MSG_SIZ], move[MSG_SIZ];
7255         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7256         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7257                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7258         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7259         AddBookMove(buf);
7260         addToBookFlag = FALSE;
7261         ClearHighlights();
7262         return;
7263     }
7264
7265     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7266 }
7267
7268 /* Common tail of UserMoveEvent and DropMenuEvent */
7269 int
7270 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7271 {
7272     char *bookHit = 0;
7273
7274     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7275         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7276         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7277         if(WhiteOnMove(currentMove)) {
7278             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7279         } else {
7280             if(!boards[currentMove][handSize-1-k][1]) return 0;
7281         }
7282     }
7283
7284     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7285        move type in caller when we know the move is a legal promotion */
7286     if(moveType == NormalMove && promoChar)
7287         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7288
7289     /* [HGM] <popupFix> The following if has been moved here from
7290        UserMoveEvent(). Because it seemed to belong here (why not allow
7291        piece drops in training games?), and because it can only be
7292        performed after it is known to what we promote. */
7293     if (gameMode == Training) {
7294       /* compare the move played on the board to the next move in the
7295        * game. If they match, display the move and the opponent's response.
7296        * If they don't match, display an error message.
7297        */
7298       int saveAnimate;
7299       Board testBoard;
7300       CopyBoard(testBoard, boards[currentMove]);
7301       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7302
7303       if (CompareBoards(testBoard, boards[currentMove+1])) {
7304         ForwardInner(currentMove+1);
7305
7306         /* Autoplay the opponent's response.
7307          * if appData.animate was TRUE when Training mode was entered,
7308          * the response will be animated.
7309          */
7310         saveAnimate = appData.animate;
7311         appData.animate = animateTraining;
7312         ForwardInner(currentMove+1);
7313         appData.animate = saveAnimate;
7314
7315         /* check for the end of the game */
7316         if (currentMove >= forwardMostMove) {
7317           gameMode = PlayFromGameFile;
7318           ModeHighlight();
7319           SetTrainingModeOff();
7320           DisplayInformation(_("End of game"));
7321         }
7322       } else {
7323         DisplayError(_("Incorrect move"), 0);
7324       }
7325       return 1;
7326     }
7327
7328   /* Ok, now we know that the move is good, so we can kill
7329      the previous line in Analysis Mode */
7330   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7331                                 && currentMove < forwardMostMove) {
7332     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7333     else forwardMostMove = currentMove;
7334   }
7335
7336   ClearMap();
7337
7338   /* If we need the chess program but it's dead, restart it */
7339   ResurrectChessProgram();
7340
7341   /* A user move restarts a paused game*/
7342   if (pausing)
7343     PauseEvent();
7344
7345   thinkOutput[0] = NULLCHAR;
7346
7347   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7348
7349   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7350     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7351     return 1;
7352   }
7353
7354   if (gameMode == BeginningOfGame) {
7355     if (appData.noChessProgram) {
7356       gameMode = EditGame;
7357       SetGameInfo();
7358     } else {
7359       char buf[MSG_SIZ];
7360       gameMode = MachinePlaysBlack;
7361       StartClocks();
7362       SetGameInfo();
7363       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7364       DisplayTitle(buf);
7365       if (first.sendName) {
7366         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7367         SendToProgram(buf, &first);
7368       }
7369       StartClocks();
7370     }
7371     ModeHighlight();
7372   }
7373
7374   /* Relay move to ICS or chess engine */
7375   if (appData.icsActive) {
7376     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7377         gameMode == IcsExamining) {
7378       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7379         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7380         SendToICS("draw ");
7381         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7382       }
7383       // also send plain move, in case ICS does not understand atomic claims
7384       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7385       ics_user_moved = 1;
7386     }
7387   } else {
7388     if (first.sendTime && (gameMode == BeginningOfGame ||
7389                            gameMode == MachinePlaysWhite ||
7390                            gameMode == MachinePlaysBlack)) {
7391       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7392     }
7393     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7394          // [HGM] book: if program might be playing, let it use book
7395         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7396         first.maybeThinking = TRUE;
7397     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7398         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7399         SendBoard(&first, currentMove+1);
7400         if(second.analyzing) {
7401             if(!second.useSetboard) SendToProgram("undo\n", &second);
7402             SendBoard(&second, currentMove+1);
7403         }
7404     } else {
7405         SendMoveToProgram(forwardMostMove-1, &first);
7406         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7407     }
7408     if (currentMove == cmailOldMove + 1) {
7409       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7410     }
7411   }
7412
7413   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7414
7415   switch (gameMode) {
7416   case EditGame:
7417     if(appData.testLegality)
7418     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7419     case MT_NONE:
7420     case MT_CHECK:
7421       break;
7422     case MT_CHECKMATE:
7423     case MT_STAINMATE:
7424       if (WhiteOnMove(currentMove)) {
7425         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7426       } else {
7427         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7428       }
7429       break;
7430     case MT_STALEMATE:
7431       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7432       break;
7433     }
7434     break;
7435
7436   case MachinePlaysBlack:
7437   case MachinePlaysWhite:
7438     /* disable certain menu options while machine is thinking */
7439     SetMachineThinkingEnables();
7440     break;
7441
7442   default:
7443     break;
7444   }
7445
7446   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7447   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7448
7449   if(bookHit) { // [HGM] book: simulate book reply
7450         static char bookMove[MSG_SIZ]; // a bit generous?
7451
7452         programStats.nodes = programStats.depth = programStats.time =
7453         programStats.score = programStats.got_only_move = 0;
7454         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7455
7456         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7457         strcat(bookMove, bookHit);
7458         HandleMachineMove(bookMove, &first);
7459   }
7460   return 1;
7461 }
7462
7463 void
7464 MarkByFEN(char *fen)
7465 {
7466         int r, f;
7467         if(!appData.markers || !appData.highlightDragging) return;
7468         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7469         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7470         while(*fen) {
7471             int s = 0;
7472             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7473             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7474             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7475             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7476             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7477             if(*fen == 'T') marker[r][f++] = 0; else
7478             if(*fen == 'Y') marker[r][f++] = 1; else
7479             if(*fen == 'G') marker[r][f++] = 3; else
7480             if(*fen == 'B') marker[r][f++] = 4; else
7481             if(*fen == 'C') marker[r][f++] = 5; else
7482             if(*fen == 'M') marker[r][f++] = 6; else
7483             if(*fen == 'W') marker[r][f++] = 7; else
7484             if(*fen == 'D') marker[r][f++] = 8; else
7485             if(*fen == 'R') marker[r][f++] = 2; else {
7486                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7487               f += s; fen -= s>0;
7488             }
7489             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7490             if(r < 0) break;
7491             fen++;
7492         }
7493         DrawPosition(TRUE, NULL);
7494 }
7495
7496 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7497
7498 void
7499 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7500 {
7501     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7502     Markers *m = (Markers *) closure;
7503     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7504                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7505         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7506                          || kind == WhiteCapturesEnPassant
7507                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7508     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7509 }
7510
7511 static int hoverSavedValid;
7512
7513 void
7514 MarkTargetSquares (int clear)
7515 {
7516   int x, y, sum=0;
7517   if(clear) { // no reason to ever suppress clearing
7518     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7519     hoverSavedValid = 0;
7520     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7521   } else {
7522     int capt = 0;
7523     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7524        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7525     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7526     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7527       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7528       if(capt)
7529       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7530     }
7531   }
7532   DrawPosition(FALSE, NULL);
7533 }
7534
7535 int
7536 Explode (Board board, int fromX, int fromY, int toX, int toY)
7537 {
7538     if(gameInfo.variant == VariantAtomic &&
7539        (board[toY][toX] != EmptySquare ||                     // capture?
7540         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7541                          board[fromY][fromX] == BlackPawn   )
7542       )) {
7543         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7544         return TRUE;
7545     }
7546     return FALSE;
7547 }
7548
7549 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7550
7551 int
7552 CanPromote (ChessSquare piece, int y)
7553 {
7554         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7555         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7556         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7557         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7558            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7559           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7560            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7561         return (piece == BlackPawn && y <= zone ||
7562                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7563                 piece == BlackLance && y <= zone ||
7564                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7565 }
7566
7567 void
7568 HoverEvent (int xPix, int yPix, int x, int y)
7569 {
7570         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7571         int r, f;
7572         if(!first.highlight) return;
7573         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7574         if(x == oldX && y == oldY) return; // only do something if we enter new square
7575         oldFromX = fromX; oldFromY = fromY;
7576         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7577           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7578             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7579           hoverSavedValid = 1;
7580         } else if(oldX != x || oldY != y) {
7581           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7582           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7583           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7584             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7585           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7586             char buf[MSG_SIZ];
7587             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7588             SendToProgram(buf, &first);
7589           }
7590           oldX = x; oldY = y;
7591 //        SetHighlights(fromX, fromY, x, y);
7592         }
7593 }
7594
7595 void ReportClick(char *action, int x, int y)
7596 {
7597         char buf[MSG_SIZ]; // Inform engine of what user does
7598         int r, f;
7599         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7600           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7601             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7602         if(!first.highlight || gameMode == EditPosition) return;
7603         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7604         SendToProgram(buf, &first);
7605 }
7606
7607 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7608 Boolean deferChoice;
7609 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7610
7611 void
7612 LeftClick (ClickType clickType, int xPix, int yPix)
7613 {
7614     int x, y;
7615     static Boolean saveAnimate;
7616     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7617     char promoChoice = NULLCHAR;
7618     ChessSquare piece;
7619     static TimeMark lastClickTime, prevClickTime;
7620
7621     if(flashing) return;
7622
7623   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7624     x = EventToSquare(xPix, BOARD_WIDTH);
7625     y = EventToSquare(yPix, BOARD_HEIGHT);
7626     if (!flipView && y >= 0) {
7627         y = BOARD_HEIGHT - 1 - y;
7628     }
7629     if (flipView && x >= 0) {
7630         x = BOARD_WIDTH - 1 - x;
7631     }
7632
7633     // map clicks in offsetted holdings back to true coords (or switch the offset)
7634     if(x == BOARD_RGHT+1) {
7635         if(handOffsets & 1) {
7636             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7637             y += handSize - BOARD_HEIGHT;
7638         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7639     }
7640     if(x == BOARD_LEFT-2) {
7641         if(!(handOffsets & 2)) {
7642             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7643             y += handSize - BOARD_HEIGHT;
7644         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7645     }
7646
7647     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7648         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7649         static int dummy;
7650         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7651         right = TRUE;
7652         return;
7653     }
7654
7655     createX = createY = -1;
7656
7657     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7658
7659     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7660
7661     if (clickType == Press) ErrorPopDown();
7662     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7663
7664     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7665         defaultPromoChoice = promoSweep;
7666         promoSweep = EmptySquare;   // terminate sweep
7667         promoDefaultAltered = TRUE;
7668         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7669     }
7670
7671     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7672         if(clickType == Release) return; // ignore upclick of click-click destination
7673         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7674         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7675         if(gameInfo.holdingsWidth &&
7676                 (WhiteOnMove(currentMove)
7677                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7678                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7679             // click in right holdings, for determining promotion piece
7680             ChessSquare p = boards[currentMove][y][x];
7681             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7682             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7683             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7684                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7685                 fromX = fromY = -1;
7686                 return;
7687             }
7688         }
7689         DrawPosition(FALSE, boards[currentMove]);
7690         return;
7691     }
7692
7693     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7694     if(clickType == Press
7695             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7696               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7697               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7698         return;
7699
7700     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7701         // could be static click on premove from-square: abort premove
7702         gotPremove = 0;
7703         ClearPremoveHighlights();
7704     }
7705
7706     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7707         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7708
7709     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7710         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7711                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7712         defaultPromoChoice = DefaultPromoChoice(side);
7713     }
7714
7715     autoQueen = appData.alwaysPromoteToQueen;
7716
7717     if (fromX == -1) {
7718       int originalY = y;
7719       gatingPiece = EmptySquare;
7720       if (clickType != Press) {
7721         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7722             DragPieceEnd(xPix, yPix); dragging = 0;
7723             DrawPosition(FALSE, NULL);
7724         }
7725         return;
7726       }
7727       doubleClick = FALSE;
7728       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7729         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7730       }
7731       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7732       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7733          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7734          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7735             /* First square */
7736             if (OKToStartUserMove(fromX, fromY)) {
7737                 second = 0;
7738                 ReportClick("lift", x, y);
7739                 MarkTargetSquares(0);
7740                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7741                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7742                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7743                     promoSweep = defaultPromoChoice;
7744                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7745                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7746                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7747                 }
7748                 if (appData.highlightDragging) {
7749                     SetHighlights(fromX, fromY, -1, -1);
7750                 } else {
7751                     ClearHighlights();
7752                 }
7753             } else fromX = fromY = -1;
7754             return;
7755         }
7756     }
7757
7758     /* fromX != -1 */
7759     if (clickType == Press && gameMode != EditPosition) {
7760         ChessSquare fromP;
7761         ChessSquare toP;
7762         int frc;
7763
7764         // ignore off-board to clicks
7765         if(y < 0 || x < 0) return;
7766
7767         /* Check if clicking again on the same color piece */
7768         fromP = boards[currentMove][fromY][fromX];
7769         toP = boards[currentMove][y][x];
7770         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7771         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7772             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7773            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7774              WhitePawn <= toP && toP <= WhiteKing &&
7775              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7776              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7777             (BlackPawn <= fromP && fromP <= BlackKing &&
7778              BlackPawn <= toP && toP <= BlackKing &&
7779              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7780              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7781             /* Clicked again on same color piece -- changed his mind */
7782             second = (x == fromX && y == fromY);
7783             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7784             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7785                 second = FALSE; // first double-click rather than scond click
7786                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7787             }
7788             promoDefaultAltered = FALSE;
7789            if(!second) MarkTargetSquares(1);
7790            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7791             if (appData.highlightDragging) {
7792                 SetHighlights(x, y, -1, -1);
7793             } else {
7794                 ClearHighlights();
7795             }
7796             if (OKToStartUserMove(x, y)) {
7797                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7798                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7799                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7800                  gatingPiece = boards[currentMove][fromY][fromX];
7801                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7802                 fromX = x;
7803                 fromY = y; dragging = 1;
7804                 if(!second) ReportClick("lift", x, y);
7805                 MarkTargetSquares(0);
7806                 DragPieceBegin(xPix, yPix, FALSE);
7807                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7808                     promoSweep = defaultPromoChoice;
7809                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7810                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7811                 }
7812             }
7813            }
7814            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7815            second = FALSE;
7816         }
7817         // ignore clicks on holdings
7818         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7819     }
7820
7821     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7822         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7823         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7824         return;
7825     }
7826
7827     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7828         DragPieceEnd(xPix, yPix); dragging = 0;
7829         if(clearFlag) {
7830             // a deferred attempt to click-click move an empty square on top of a piece
7831             boards[currentMove][y][x] = EmptySquare;
7832             ClearHighlights();
7833             DrawPosition(FALSE, boards[currentMove]);
7834             fromX = fromY = -1; clearFlag = 0;
7835             return;
7836         }
7837         if (appData.animateDragging) {
7838             /* Undo animation damage if any */
7839             DrawPosition(FALSE, NULL);
7840         }
7841         if (second) {
7842             /* Second up/down in same square; just abort move */
7843             second = 0;
7844             fromX = fromY = -1;
7845             gatingPiece = EmptySquare;
7846             ClearHighlights();
7847             gotPremove = 0;
7848             ClearPremoveHighlights();
7849             MarkTargetSquares(-1);
7850             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7851         } else {
7852             /* First upclick in same square; start click-click mode */
7853             SetHighlights(x, y, -1, -1);
7854         }
7855         return;
7856     }
7857
7858     clearFlag = 0;
7859
7860     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7861        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7862         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7863         DisplayMessage(_("only marked squares are legal"),"");
7864         DrawPosition(TRUE, NULL);
7865         return; // ignore to-click
7866     }
7867
7868     /* we now have a different from- and (possibly off-board) to-square */
7869     /* Completed move */
7870     if(!sweepSelecting) {
7871         toX = x;
7872         toY = y;
7873     }
7874
7875     piece = boards[currentMove][fromY][fromX];
7876
7877     saveAnimate = appData.animate;
7878     if (clickType == Press) {
7879         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7880         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7881             // must be Edit Position mode with empty-square selected
7882             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7883             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7884             return;
7885         }
7886         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7887             return;
7888         }
7889         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7890             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7891         } else
7892         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7893         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7894           if(appData.sweepSelect) {
7895             promoSweep = defaultPromoChoice;
7896             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7897             selectFlag = 0; lastX = xPix; lastY = yPix;
7898             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7899             saveFlash = appData.flashCount; appData.flashCount = 0;
7900             Sweep(0); // Pawn that is going to promote: preview promotion piece
7901             sweepSelecting = 1;
7902             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7903             MarkTargetSquares(1);
7904           }
7905           return; // promo popup appears on up-click
7906         }
7907         /* Finish clickclick move */
7908         if (appData.animate || appData.highlightLastMove) {
7909             SetHighlights(fromX, fromY, toX, toY);
7910         } else {
7911             ClearHighlights();
7912         }
7913         MarkTargetSquares(1);
7914     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7915         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7916         *promoRestrict = 0; appData.flashCount = saveFlash;
7917         if (appData.animate || appData.highlightLastMove) {
7918             SetHighlights(fromX, fromY, toX, toY);
7919         } else {
7920             ClearHighlights();
7921         }
7922         MarkTargetSquares(1);
7923     } else {
7924 #if 0
7925 // [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
7926         /* Finish drag move */
7927         if (appData.highlightLastMove) {
7928             SetHighlights(fromX, fromY, toX, toY);
7929         } else {
7930             ClearHighlights();
7931         }
7932 #endif
7933         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7934           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7935         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7936         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7937           dragging *= 2;            // flag button-less dragging if we are dragging
7938           MarkTargetSquares(1);
7939           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7940           else {
7941             kill2X = killX; kill2Y = killY;
7942             killX = x; killY = y;     // remember this square as intermediate
7943             ReportClick("put", x, y); // and inform engine
7944             ReportClick("lift", x, y);
7945             MarkTargetSquares(0);
7946             return;
7947           }
7948         }
7949         DragPieceEnd(xPix, yPix); dragging = 0;
7950         /* Don't animate move and drag both */
7951         appData.animate = FALSE;
7952         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7953     }
7954
7955     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7956     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7957         ChessSquare piece = boards[currentMove][fromY][fromX];
7958         if(gameMode == EditPosition && piece != EmptySquare &&
7959            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7960             int n;
7961
7962             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7963                 n = PieceToNumber(piece - (int)BlackPawn);
7964                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7965                 boards[currentMove][handSize-1 - n][0] = piece;
7966                 boards[currentMove][handSize-1 - n][1]++;
7967             } else
7968             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7969                 n = PieceToNumber(piece);
7970                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7971                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7972                 boards[currentMove][n][BOARD_WIDTH-2]++;
7973             }
7974             boards[currentMove][fromY][fromX] = EmptySquare;
7975         }
7976         ClearHighlights();
7977         fromX = fromY = -1;
7978         MarkTargetSquares(1);
7979         DrawPosition(TRUE, boards[currentMove]);
7980         return;
7981     }
7982
7983     // off-board moves should not be highlighted
7984     if(x < 0 || y < 0) {
7985         ClearHighlights();
7986         DrawPosition(FALSE, NULL);
7987     } else ReportClick("put", x, y);
7988
7989     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7990  }
7991
7992     if(legal[toY][toX] == 2) { // highlight-induced promotion
7993         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7994         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7995     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7996       if(!*promoRestrict) {           // but has not done that yet
7997         deferChoice = TRUE;           // set up retry for when it does
7998         return;                       // and wait for that
7999       }
8000       promoChoice = ToLower(*promoRestrict); // force engine's choice
8001       deferChoice = FALSE;
8002     }
8003
8004     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
8005         SetHighlights(fromX, fromY, toX, toY);
8006         MarkTargetSquares(1);
8007         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
8008             // [HGM] super: promotion to captured piece selected from holdings
8009             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
8010             promotionChoice = TRUE;
8011             // kludge follows to temporarily execute move on display, without promoting yet
8012             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8013             boards[currentMove][toY][toX] = p;
8014             DrawPosition(FALSE, boards[currentMove]);
8015             boards[currentMove][fromY][fromX] = p; // take back, but display stays
8016             boards[currentMove][toY][toX] = q;
8017             DisplayMessage("Click in holdings to choose piece", "");
8018             return;
8019         }
8020         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8021         PromotionPopUp(promoChoice);
8022     } else {
8023         int oldMove = currentMove;
8024         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8025         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8026         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8027         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8028         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8029            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8030             DrawPosition(TRUE, boards[currentMove]);
8031         else DrawPosition(FALSE, NULL);
8032         fromX = fromY = -1;
8033         flashing = 0;
8034     }
8035     appData.animate = saveAnimate;
8036     if (appData.animate || appData.animateDragging) {
8037         /* Undo animation damage if needed */
8038 //      DrawPosition(FALSE, NULL);
8039     }
8040 }
8041
8042 int
8043 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8044 {   // front-end-free part taken out of PieceMenuPopup
8045     int whichMenu; int xSqr, ySqr;
8046
8047     if(seekGraphUp) { // [HGM] seekgraph
8048         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8049         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8050         return -2;
8051     }
8052
8053     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8054          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8055         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8056         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8057         if(action == Press)   {
8058             originalFlip = flipView;
8059             flipView = !flipView; // temporarily flip board to see game from partners perspective
8060             DrawPosition(TRUE, partnerBoard);
8061             DisplayMessage(partnerStatus, "");
8062             partnerUp = TRUE;
8063         } else if(action == Release) {
8064             flipView = originalFlip;
8065             DrawPosition(TRUE, boards[currentMove]);
8066             partnerUp = FALSE;
8067         }
8068         return -2;
8069     }
8070
8071     xSqr = EventToSquare(x, BOARD_WIDTH);
8072     ySqr = EventToSquare(y, BOARD_HEIGHT);
8073     if (action == Release) {
8074         if(pieceSweep != EmptySquare) {
8075             EditPositionMenuEvent(pieceSweep, toX, toY);
8076             pieceSweep = EmptySquare;
8077         } else UnLoadPV(); // [HGM] pv
8078     }
8079     if (action != Press) return -2; // return code to be ignored
8080     switch (gameMode) {
8081       case IcsExamining:
8082         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8083       case EditPosition:
8084         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8085         if (xSqr < 0 || ySqr < 0) return -1;
8086         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8087         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8088         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8089             ChessSquare p = boards[currentMove][ySqr][xSqr];
8090             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8091             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8092             return -2;
8093         }
8094         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8095         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8096         NextPiece(0);
8097         return 2; // grab
8098       case IcsObserving:
8099         if(!appData.icsEngineAnalyze) return -1;
8100       case IcsPlayingWhite:
8101       case IcsPlayingBlack:
8102         if(!appData.zippyPlay) goto noZip;
8103       case AnalyzeMode:
8104       case AnalyzeFile:
8105       case MachinePlaysWhite:
8106       case MachinePlaysBlack:
8107       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8108         if (!appData.dropMenu) {
8109           LoadPV(x, y);
8110           return 2; // flag front-end to grab mouse events
8111         }
8112         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8113            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8114       case EditGame:
8115       noZip:
8116         if (xSqr < 0 || ySqr < 0) return -1;
8117         if (!appData.dropMenu || appData.testLegality &&
8118             gameInfo.variant != VariantBughouse &&
8119             gameInfo.variant != VariantCrazyhouse) return -1;
8120         whichMenu = 1; // drop menu
8121         break;
8122       default:
8123         return -1;
8124     }
8125
8126     if (((*fromX = xSqr) < 0) ||
8127         ((*fromY = ySqr) < 0)) {
8128         *fromX = *fromY = -1;
8129         return -1;
8130     }
8131     if (flipView)
8132       *fromX = BOARD_WIDTH - 1 - *fromX;
8133     else
8134       *fromY = BOARD_HEIGHT - 1 - *fromY;
8135
8136     return whichMenu;
8137 }
8138
8139 void
8140 Wheel (int dir, int x, int y)
8141 {
8142     if(gameMode == EditPosition) {
8143         int xSqr = EventToSquare(x, BOARD_WIDTH);
8144         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8145         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8146         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8147         do {
8148             boards[currentMove][ySqr][xSqr] += dir;
8149             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8150             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8151         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8152         DrawPosition(FALSE, boards[currentMove]);
8153     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8154 }
8155
8156 void
8157 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8158 {
8159 //    char * hint = lastHint;
8160     FrontEndProgramStats stats;
8161
8162     stats.which = cps == &first ? 0 : 1;
8163     stats.depth = cpstats->depth;
8164     stats.nodes = cpstats->nodes;
8165     stats.score = cpstats->score;
8166     stats.time = cpstats->time;
8167     stats.pv = cpstats->movelist;
8168     stats.hint = lastHint;
8169     stats.an_move_index = 0;
8170     stats.an_move_count = 0;
8171
8172     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8173         stats.hint = cpstats->move_name;
8174         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8175         stats.an_move_count = cpstats->nr_moves;
8176     }
8177
8178     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
8179
8180     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8181         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8182
8183     SetProgramStats( &stats );
8184 }
8185
8186 void
8187 ClearEngineOutputPane (int which)
8188 {
8189     static FrontEndProgramStats dummyStats;
8190     dummyStats.which = which;
8191     dummyStats.pv = "#";
8192     SetProgramStats( &dummyStats );
8193 }
8194
8195 #define MAXPLAYERS 500
8196
8197 char *
8198 TourneyStandings (int display)
8199 {
8200     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8201     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8202     char result, *p, *names[MAXPLAYERS];
8203
8204     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8205         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8206     names[0] = p = strdup(appData.participants);
8207     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8208
8209     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8210
8211     while(result = appData.results[nr]) {
8212         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8213         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8214         wScore = bScore = 0;
8215         switch(result) {
8216           case '+': wScore = 2; break;
8217           case '-': bScore = 2; break;
8218           case '=': wScore = bScore = 1; break;
8219           case ' ':
8220           case '*': return strdup("busy"); // tourney not finished
8221         }
8222         score[w] += wScore;
8223         score[b] += bScore;
8224         games[w]++;
8225         games[b]++;
8226         nr++;
8227     }
8228     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8229     for(w=0; w<nPlayers; w++) {
8230         bScore = -1;
8231         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8232         ranking[w] = b; points[w] = bScore; score[b] = -2;
8233     }
8234     p = malloc(nPlayers*34+1);
8235     for(w=0; w<nPlayers && w<display; w++)
8236         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8237     free(names[0]);
8238     return p;
8239 }
8240
8241 void
8242 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8243 {       // count all piece types
8244         int p, f, r;
8245         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8246         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8247         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8248                 p = board[r][f];
8249                 pCnt[p]++;
8250                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8251                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8252                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8253                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8254                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8255                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8256         }
8257 }
8258
8259 int
8260 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8261 {
8262         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8263         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8264
8265         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8266         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8267         if(myPawns == 2 && nMine == 3) // KPP
8268             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8269         if(myPawns == 1 && nMine == 2) // KP
8270             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8271         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8272             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8273         if(myPawns) return FALSE;
8274         if(pCnt[WhiteRook+side])
8275             return pCnt[BlackRook-side] ||
8276                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8277                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8278                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8279         if(pCnt[WhiteCannon+side]) {
8280             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8281             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8282         }
8283         if(pCnt[WhiteKnight+side])
8284             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8285         return FALSE;
8286 }
8287
8288 int
8289 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8290 {
8291         VariantClass v = gameInfo.variant;
8292
8293         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8294         if(v == VariantShatranj) return TRUE; // always winnable through baring
8295         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8296         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8297
8298         if(v == VariantXiangqi) {
8299                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8300
8301                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8302                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8303                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8304                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8305                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8306                 if(stale) // we have at least one last-rank P plus perhaps C
8307                     return majors // KPKX
8308                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8309                 else // KCA*E*
8310                     return pCnt[WhiteFerz+side] // KCAK
8311                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8312                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8313                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8314
8315         } else if(v == VariantKnightmate) {
8316                 if(nMine == 1) return FALSE;
8317                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8318         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8319                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8320
8321                 if(nMine == 1) return FALSE; // bare King
8322                 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
8323                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8324                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8325                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8326                 if(pCnt[WhiteKnight+side])
8327                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8328                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8329                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8330                 if(nBishops)
8331                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8332                 if(pCnt[WhiteAlfil+side])
8333                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8334                 if(pCnt[WhiteWazir+side])
8335                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8336         }
8337
8338         return TRUE;
8339 }
8340
8341 int
8342 CompareWithRights (Board b1, Board b2)
8343 {
8344     int rights = 0;
8345     if(!CompareBoards(b1, b2)) return FALSE;
8346     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8347     /* compare castling rights */
8348     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8349            rights++; /* King lost rights, while rook still had them */
8350     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8351         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8352            rights++; /* but at least one rook lost them */
8353     }
8354     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8355            rights++;
8356     if( b1[CASTLING][5] != NoRights ) {
8357         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8358            rights++;
8359     }
8360     return rights == 0;
8361 }
8362
8363 int
8364 Adjudicate (ChessProgramState *cps)
8365 {       // [HGM] some adjudications useful with buggy engines
8366         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8367         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8368         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8369         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8370         int k, drop, count = 0; static int bare = 1;
8371         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8372         Boolean canAdjudicate = !appData.icsActive;
8373
8374         // most tests only when we understand the game, i.e. legality-checking on
8375             if( appData.testLegality )
8376             {   /* [HGM] Some more adjudications for obstinate engines */
8377                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8378                 static int moveCount = 6;
8379                 ChessMove result;
8380                 char *reason = NULL;
8381
8382                 /* Count what is on board. */
8383                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8384
8385                 /* Some material-based adjudications that have to be made before stalemate test */
8386                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8387                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8388                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8389                      if(canAdjudicate && appData.checkMates) {
8390                          if(engineOpponent)
8391                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8392                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8393                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8394                          return 1;
8395                      }
8396                 }
8397
8398                 /* Bare King in Shatranj (loses) or Losers (wins) */
8399                 if( nrW == 1 || nrB == 1) {
8400                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8401                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8402                      if(canAdjudicate && appData.checkMates) {
8403                          if(engineOpponent)
8404                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8405                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8406                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8407                          return 1;
8408                      }
8409                   } else
8410                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8411                   {    /* bare King */
8412                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8413                         if(canAdjudicate && appData.checkMates) {
8414                             /* but only adjudicate if adjudication enabled */
8415                             if(engineOpponent)
8416                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8417                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8418                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8419                             return 1;
8420                         }
8421                   }
8422                 } else bare = 1;
8423
8424
8425             // don't wait for engine to announce game end if we can judge ourselves
8426             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8427               case MT_CHECK:
8428                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8429                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8430                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8431                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8432                             checkCnt++;
8433                         if(checkCnt >= 2) {
8434                             reason = "Xboard adjudication: 3rd check";
8435                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8436                             break;
8437                         }
8438                     }
8439                 }
8440               case MT_NONE:
8441               default:
8442                 break;
8443               case MT_STEALMATE:
8444               case MT_STALEMATE:
8445               case MT_STAINMATE:
8446                 reason = "Xboard adjudication: Stalemate";
8447                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8448                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8449                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8450                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8451                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8452                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8453                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8454                                                                         EP_CHECKMATE : EP_WINS);
8455                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8456                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8457                 }
8458                 break;
8459               case MT_CHECKMATE:
8460                 reason = "Xboard adjudication: Checkmate";
8461                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8462                 if(gameInfo.variant == VariantShogi) {
8463                     if(forwardMostMove > backwardMostMove
8464                        && moveList[forwardMostMove-1][1] == '@'
8465                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8466                         reason = "XBoard adjudication: pawn-drop mate";
8467                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8468                     }
8469                 }
8470                 break;
8471             }
8472
8473                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8474                     case EP_STALEMATE:
8475                         result = GameIsDrawn; break;
8476                     case EP_CHECKMATE:
8477                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8478                     case EP_WINS:
8479                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8480                     default:
8481                         result = EndOfFile;
8482                 }
8483                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8484                     if(engineOpponent)
8485                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8486                     GameEnds( result, reason, GE_XBOARD );
8487                     return 1;
8488                 }
8489
8490                 /* Next absolutely insufficient mating material. */
8491                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8492                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8493                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8494
8495                      /* always flag draws, for judging claims */
8496                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8497
8498                      if(canAdjudicate && appData.materialDraws) {
8499                          /* but only adjudicate them if adjudication enabled */
8500                          if(engineOpponent) {
8501                            SendToProgram("force\n", engineOpponent); // suppress reply
8502                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8503                          }
8504                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8505                          return 1;
8506                      }
8507                 }
8508
8509                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8510                 if(gameInfo.variant == VariantXiangqi ?
8511                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8512                  : nrW + nrB == 4 &&
8513                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8514                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8515                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8516                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8517                    ) ) {
8518                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8519                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8520                           if(engineOpponent) {
8521                             SendToProgram("force\n", engineOpponent); // suppress reply
8522                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8523                           }
8524                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8525                           return 1;
8526                      }
8527                 } else moveCount = 6;
8528
8529                 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8530                   (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8531                     int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8532                     count = forwardMostMove;
8533                     while(count >= backwardMostMove) {
8534                         int np = nr[WhitePawn] + nr[BlackPawn];
8535                         if(wom) mine = nrW, his = nrB, c = BlackPawn;
8536                         else    mine = nrB, his = nrW, c = WhitePawn;
8537                         if(mine > 1 && np) { count++; break; }
8538                         if(mine > 1) maxcnt = 64; else
8539                         maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8540                                                             nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8541                         while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8542                         if(count == backwardMostMove) break;
8543                         if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8544                         Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8545                     }
8546                     if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8547                         boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8548                         if(canAdjudicate && appData.ruleMoves >= 0) {
8549                             GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8550                             return 1;
8551                         }
8552                     }
8553                 }
8554             }
8555
8556         // Repetition draws and 50-move rule can be applied independently of legality testing
8557
8558                 /* Check for rep-draws */
8559                 count = 0;
8560                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8561                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8562                 for(k = forwardMostMove-2;
8563                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8564                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8565                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8566                     k-=2)
8567                 {   int rights=0;
8568                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8569                         /* compare castling rights */
8570                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8571                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8572                                 rights++; /* King lost rights, while rook still had them */
8573                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8574                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8575                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8576                                    rights++; /* but at least one rook lost them */
8577                         }
8578                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8579                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8580                                 rights++;
8581                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8582                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8583                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8584                                    rights++;
8585                         }
8586                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8587                             && appData.drawRepeats > 1) {
8588                              /* adjudicate after user-specified nr of repeats */
8589                              int result = GameIsDrawn;
8590                              char *details = "XBoard adjudication: repetition draw";
8591                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8592                                 // [HGM] xiangqi: check for forbidden perpetuals
8593                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8594                                 for(m=forwardMostMove; m>k; m-=2) {
8595                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8596                                         ourPerpetual = 0; // the current mover did not always check
8597                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8598                                         hisPerpetual = 0; // the opponent did not always check
8599                                 }
8600                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8601                                                                         ourPerpetual, hisPerpetual);
8602                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8603                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8604                                     details = "Xboard adjudication: perpetual checking";
8605                                 } else
8606                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8607                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8608                                 } else
8609                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8610                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8611                                         result = BlackWins;
8612                                         details = "Xboard adjudication: repetition";
8613                                     }
8614                                 } else // it must be XQ
8615                                 // Now check for perpetual chases
8616                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8617                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8618                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8619                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8620                                         static char resdet[MSG_SIZ];
8621                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8622                                         details = resdet;
8623                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8624                                     } else
8625                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8626                                         break; // Abort repetition-checking loop.
8627                                 }
8628                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8629                              }
8630                              if(engineOpponent) {
8631                                SendToProgram("force\n", engineOpponent); // suppress reply
8632                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8633                              }
8634                              GameEnds( result, details, GE_XBOARD );
8635                              return 1;
8636                         }
8637                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8638                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8639                     }
8640                 }
8641
8642                 /* Now we test for 50-move draws. Determine ply count */
8643                 count = forwardMostMove;
8644                 /* look for last irreversble move */
8645                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8646                     count--;
8647                 /* if we hit starting position, add initial plies */
8648                 if( count == backwardMostMove )
8649                     count -= initialRulePlies;
8650                 count = forwardMostMove - count;
8651                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8652                         // adjust reversible move counter for checks in Xiangqi
8653                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8654                         if(i < backwardMostMove) i = backwardMostMove;
8655                         while(i <= forwardMostMove) {
8656                                 lastCheck = inCheck; // check evasion does not count
8657                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8658                                 if(inCheck || lastCheck) count--; // check does not count
8659                                 i++;
8660                         }
8661                 }
8662                 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8663                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8664                          /* this is used to judge if draw claims are legal */
8665                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8666                          if(engineOpponent) {
8667                            SendToProgram("force\n", engineOpponent); // suppress reply
8668                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8669                          }
8670                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8671                          return 1;
8672                 }
8673
8674                 /* if draw offer is pending, treat it as a draw claim
8675                  * when draw condition present, to allow engines a way to
8676                  * claim draws before making their move to avoid a race
8677                  * condition occurring after their move
8678                  */
8679                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8680                          char *p = NULL;
8681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8682                              p = "Draw claim: 50-move rule";
8683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8684                              p = "Draw claim: 3-fold repetition";
8685                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8686                              p = "Draw claim: insufficient mating material";
8687                          if( p != NULL && canAdjudicate) {
8688                              if(engineOpponent) {
8689                                SendToProgram("force\n", engineOpponent); // suppress reply
8690                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8691                              }
8692                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8693                              return 1;
8694                          }
8695                 }
8696
8697                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8698                     if(engineOpponent) {
8699                       SendToProgram("force\n", engineOpponent); // suppress reply
8700                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8701                     }
8702                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8703                     return 1;
8704                 }
8705         return 0;
8706 }
8707
8708 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8709 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8710 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8711
8712 static int
8713 BitbaseProbe ()
8714 {
8715     int pieces[10], squares[10], cnt=0, r, f, res;
8716     static int loaded;
8717     static PPROBE_EGBB probeBB;
8718     if(!appData.testLegality) return 10;
8719     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8720     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8721     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8722     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8723         ChessSquare piece = boards[forwardMostMove][r][f];
8724         int black = (piece >= BlackPawn);
8725         int type = piece - black*BlackPawn;
8726         if(piece == EmptySquare) continue;
8727         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8728         if(type == WhiteKing) type = WhiteQueen + 1;
8729         type = egbbCode[type];
8730         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8731         pieces[cnt] = type + black*6;
8732         if(++cnt > 5) return 11;
8733     }
8734     pieces[cnt] = squares[cnt] = 0;
8735     // probe EGBB
8736     if(loaded == 2) return 13; // loading failed before
8737     if(loaded == 0) {
8738         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8739         HMODULE lib;
8740         PLOAD_EGBB loadBB;
8741         loaded = 2; // prepare for failure
8742         if(!path) return 13; // no egbb installed
8743         strncpy(buf, path + 8, MSG_SIZ);
8744         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8745         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8746         lib = LoadLibrary(buf);
8747         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8748         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8749         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8750         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8751         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8752         loaded = 1; // success!
8753     }
8754     res = probeBB(forwardMostMove & 1, pieces, squares);
8755     return res > 0 ? 1 : res < 0 ? -1 : 0;
8756 }
8757
8758 char *
8759 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8760 {   // [HGM] book: this routine intercepts moves to simulate book replies
8761     char *bookHit = NULL;
8762
8763     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8764         char buf[MSG_SIZ];
8765         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8766         SendToProgram(buf, cps);
8767     }
8768     //first determine if the incoming move brings opponent into his book
8769     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8770         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8771     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8772     if(bookHit != NULL && !cps->bookSuspend) {
8773         // make sure opponent is not going to reply after receiving move to book position
8774         SendToProgram("force\n", cps);
8775         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8776     }
8777     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8778     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8779     // now arrange restart after book miss
8780     if(bookHit) {
8781         // after a book hit we never send 'go', and the code after the call to this routine
8782         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8783         char buf[MSG_SIZ], *move = bookHit;
8784         if(cps->useSAN) {
8785             int fromX, fromY, toX, toY;
8786             char promoChar;
8787             ChessMove moveType;
8788             move = buf + 30;
8789             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8790                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8791                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8792                                     PosFlags(forwardMostMove),
8793                                     fromY, fromX, toY, toX, promoChar, move);
8794             } else {
8795                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8796                 bookHit = NULL;
8797             }
8798         }
8799         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8800         SendToProgram(buf, cps);
8801         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8802     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8803         SendToProgram("go\n", cps);
8804         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8805     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8806         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8807             SendToProgram("go\n", cps);
8808         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8809     }
8810     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8811 }
8812
8813 int
8814 LoadError (char *errmess, ChessProgramState *cps)
8815 {   // unloads engine and switches back to -ncp mode if it was first
8816     if(cps->initDone) return FALSE;
8817     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8818     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8819     cps->pr = NoProc;
8820     if(cps == &first) {
8821         appData.noChessProgram = TRUE;
8822         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8823         gameMode = BeginningOfGame; ModeHighlight();
8824         SetNCPMode();
8825     }
8826     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8827     DisplayMessage("", ""); // erase waiting message
8828     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8829     return TRUE;
8830 }
8831
8832 char *savedMessage;
8833 ChessProgramState *savedState;
8834 void
8835 DeferredBookMove (void)
8836 {
8837         if(savedState->lastPing != savedState->lastPong)
8838                     ScheduleDelayedEvent(DeferredBookMove, 10);
8839         else
8840         HandleMachineMove(savedMessage, savedState);
8841 }
8842
8843 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8844 static ChessProgramState *stalledEngine;
8845 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8846
8847 void
8848 HandleMachineMove (char *message, ChessProgramState *cps)
8849 {
8850     static char firstLeg[20], legs;
8851     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8852     char realname[MSG_SIZ];
8853     int fromX, fromY, toX, toY;
8854     ChessMove moveType;
8855     char promoChar, roar;
8856     char *p, *pv=buf1;
8857     int oldError;
8858     char *bookHit;
8859
8860     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8861         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8862         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8863             DisplayError(_("Invalid pairing from pairing engine"), 0);
8864             return;
8865         }
8866         pairingReceived = 1;
8867         NextMatchGame();
8868         return; // Skim the pairing messages here.
8869     }
8870
8871     oldError = cps->userError; cps->userError = 0;
8872
8873 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8874     /*
8875      * Kludge to ignore BEL characters
8876      */
8877     while (*message == '\007') message++;
8878
8879     /*
8880      * [HGM] engine debug message: ignore lines starting with '#' character
8881      */
8882     if(cps->debug && *message == '#') return;
8883
8884     /*
8885      * Look for book output
8886      */
8887     if (cps == &first && bookRequested) {
8888         if (message[0] == '\t' || message[0] == ' ') {
8889             /* Part of the book output is here; append it */
8890             strcat(bookOutput, message);
8891             strcat(bookOutput, "  \n");
8892             return;
8893         } else if (bookOutput[0] != NULLCHAR) {
8894             /* All of book output has arrived; display it */
8895             char *p = bookOutput;
8896             while (*p != NULLCHAR) {
8897                 if (*p == '\t') *p = ' ';
8898                 p++;
8899             }
8900             DisplayInformation(bookOutput);
8901             bookRequested = FALSE;
8902             /* Fall through to parse the current output */
8903         }
8904     }
8905
8906     /*
8907      * Look for machine move.
8908      */
8909     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8910         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8911     {
8912         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8913             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8914             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8915             stalledEngine = cps;
8916             if(appData.ponderNextMove) { // bring opponent out of ponder
8917                 if(gameMode == TwoMachinesPlay) {
8918                     if(cps->other->pause)
8919                         PauseEngine(cps->other);
8920                     else
8921                         SendToProgram("easy\n", cps->other);
8922                 }
8923             }
8924             StopClocks();
8925             return;
8926         }
8927
8928       if(cps->usePing) {
8929
8930         /* This method is only useful on engines that support ping */
8931         if(abortEngineThink) {
8932             if (appData.debugMode) {
8933                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8934             }
8935             SendToProgram("undo\n", cps);
8936             return;
8937         }
8938
8939         if (cps->lastPing != cps->lastPong) {
8940             /* Extra move from before last new; ignore */
8941             if (appData.debugMode) {
8942                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8943             }
8944           return;
8945         }
8946
8947       } else {
8948
8949         int machineWhite = FALSE;
8950
8951         switch (gameMode) {
8952           case BeginningOfGame:
8953             /* Extra move from before last reset; ignore */
8954             if (appData.debugMode) {
8955                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8956             }
8957             return;
8958
8959           case EndOfGame:
8960           case IcsIdle:
8961           default:
8962             /* Extra move after we tried to stop.  The mode test is
8963                not a reliable way of detecting this problem, but it's
8964                the best we can do on engines that don't support ping.
8965             */
8966             if (appData.debugMode) {
8967                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8968                         cps->which, gameMode);
8969             }
8970             SendToProgram("undo\n", cps);
8971             return;
8972
8973           case MachinePlaysWhite:
8974           case IcsPlayingWhite:
8975             machineWhite = TRUE;
8976             break;
8977
8978           case MachinePlaysBlack:
8979           case IcsPlayingBlack:
8980             machineWhite = FALSE;
8981             break;
8982
8983           case TwoMachinesPlay:
8984             machineWhite = (cps->twoMachinesColor[0] == 'w');
8985             break;
8986         }
8987         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8988             if (appData.debugMode) {
8989                 fprintf(debugFP,
8990                         "Ignoring move out of turn by %s, gameMode %d"
8991                         ", forwardMost %d\n",
8992                         cps->which, gameMode, forwardMostMove);
8993             }
8994             return;
8995         }
8996       }
8997
8998         if(cps->alphaRank) AlphaRank(machineMove, 4);
8999
9000         // [HGM] lion: (some very limited) support for Alien protocol
9001         killX = killY = kill2X = kill2Y = -1;
9002         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
9003             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
9004             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
9005             return;
9006         }
9007         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
9008             char *q = strchr(p+1, ',');            // second comma?
9009             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
9010             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
9011             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9012         }
9013         if(firstLeg[0]) { // there was a previous leg;
9014             // only support case where same piece makes two step
9015             char buf[20], *p = machineMove+1, *q = buf+1, f;
9016             safeStrCpy(buf, machineMove, 20);
9017             while(isdigit(*q)) q++; // find start of to-square
9018             safeStrCpy(machineMove, firstLeg, 20);
9019             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9020             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
9021             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
9022             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9023             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9024             firstLeg[0] = NULLCHAR; legs = 0;
9025         }
9026
9027         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9028                               &fromX, &fromY, &toX, &toY, &promoChar)) {
9029             /* Machine move could not be parsed; ignore it. */
9030           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9031                     machineMove, _(cps->which));
9032             DisplayMoveError(buf1);
9033             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9034                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9035             if (gameMode == TwoMachinesPlay) {
9036               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9037                        buf1, GE_XBOARD);
9038             }
9039             return;
9040         }
9041
9042         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9043         /* So we have to redo legality test with true e.p. status here,  */
9044         /* to make sure an illegal e.p. capture does not slip through,   */
9045         /* to cause a forfeit on a justified illegal-move complaint      */
9046         /* of the opponent.                                              */
9047         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9048            ChessMove moveType;
9049            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9050                              fromY, fromX, toY, toX, promoChar);
9051             if(moveType == IllegalMove) {
9052               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9053                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9054                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9055                            buf1, GE_XBOARD);
9056                 return;
9057            } else if(!appData.fischerCastling)
9058            /* [HGM] Kludge to handle engines that send FRC-style castling
9059               when they shouldn't (like TSCP-Gothic) */
9060            switch(moveType) {
9061              case WhiteASideCastleFR:
9062              case BlackASideCastleFR:
9063                toX+=2;
9064                currentMoveString[2]++;
9065                break;
9066              case WhiteHSideCastleFR:
9067              case BlackHSideCastleFR:
9068                toX--;
9069                currentMoveString[2]--;
9070                break;
9071              default: ; // nothing to do, but suppresses warning of pedantic compilers
9072            }
9073         }
9074         hintRequested = FALSE;
9075         lastHint[0] = NULLCHAR;
9076         bookRequested = FALSE;
9077         /* Program may be pondering now */
9078         cps->maybeThinking = TRUE;
9079         if (cps->sendTime == 2) cps->sendTime = 1;
9080         if (cps->offeredDraw) cps->offeredDraw--;
9081
9082         /* [AS] Save move info*/
9083         pvInfoList[ forwardMostMove ].score = programStats.score;
9084         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9085         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9086
9087         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9088
9089         /* Test suites abort the 'game' after one move */
9090         if(*appData.finger) {
9091            static FILE *f;
9092            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9093            if(!f) f = fopen(appData.finger, "w");
9094            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9095            else { DisplayFatalError("Bad output file", errno, 0); return; }
9096            free(fen);
9097            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9098         }
9099         if(appData.epd) {
9100            if(solvingTime >= 0) {
9101               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9102               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9103            } else {
9104               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9105               if(solvingTime == -2) second.matchWins++;
9106            }
9107            OutputKibitz(2, buf1);
9108            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9109         }
9110
9111         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9112         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9113             int count = 0;
9114
9115             while( count < adjudicateLossPlies ) {
9116                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9117
9118                 if( count & 1 ) {
9119                     score = -score; /* Flip score for winning side */
9120                 }
9121
9122                 if( score > appData.adjudicateLossThreshold ) {
9123                     break;
9124                 }
9125
9126                 count++;
9127             }
9128
9129             if( count >= adjudicateLossPlies ) {
9130                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9131
9132                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9133                     "Xboard adjudication",
9134                     GE_XBOARD );
9135
9136                 return;
9137             }
9138         }
9139
9140         if(Adjudicate(cps)) {
9141             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9142             return; // [HGM] adjudicate: for all automatic game ends
9143         }
9144
9145 #if ZIPPY
9146         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9147             first.initDone) {
9148           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9149                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9150                 SendToICS("draw ");
9151                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9152           }
9153           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9154           ics_user_moved = 1;
9155           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9156                 char buf[3*MSG_SIZ];
9157
9158                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9159                         programStats.score / 100.,
9160                         programStats.depth,
9161                         programStats.time / 100.,
9162                         (unsigned int)programStats.nodes,
9163                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9164                         programStats.movelist);
9165                 SendToICS(buf);
9166           }
9167         }
9168 #endif
9169
9170         /* [AS] Clear stats for next move */
9171         ClearProgramStats();
9172         thinkOutput[0] = NULLCHAR;
9173         hiddenThinkOutputState = 0;
9174
9175         bookHit = NULL;
9176         if (gameMode == TwoMachinesPlay) {
9177             /* [HGM] relaying draw offers moved to after reception of move */
9178             /* and interpreting offer as claim if it brings draw condition */
9179             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9180                 SendToProgram("draw\n", cps->other);
9181             }
9182             if (cps->other->sendTime) {
9183                 SendTimeRemaining(cps->other,
9184                                   cps->other->twoMachinesColor[0] == 'w');
9185             }
9186             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9187             if (firstMove && !bookHit) {
9188                 firstMove = FALSE;
9189                 if (cps->other->useColors) {
9190                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9191                 }
9192                 SendToProgram("go\n", cps->other);
9193             }
9194             cps->other->maybeThinking = TRUE;
9195         }
9196
9197         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9198
9199         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9200
9201         if (!pausing && appData.ringBellAfterMoves) {
9202             if(!roar) RingBell();
9203         }
9204
9205         /*
9206          * Reenable menu items that were disabled while
9207          * machine was thinking
9208          */
9209         if (gameMode != TwoMachinesPlay)
9210             SetUserThinkingEnables();
9211
9212         // [HGM] book: after book hit opponent has received move and is now in force mode
9213         // force the book reply into it, and then fake that it outputted this move by jumping
9214         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9215         if(bookHit) {
9216                 static char bookMove[MSG_SIZ]; // a bit generous?
9217
9218                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9219                 strcat(bookMove, bookHit);
9220                 message = bookMove;
9221                 cps = cps->other;
9222                 programStats.nodes = programStats.depth = programStats.time =
9223                 programStats.score = programStats.got_only_move = 0;
9224                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9225
9226                 if(cps->lastPing != cps->lastPong) {
9227                     savedMessage = message; // args for deferred call
9228                     savedState = cps;
9229                     ScheduleDelayedEvent(DeferredBookMove, 10);
9230                     return;
9231                 }
9232                 goto FakeBookMove;
9233         }
9234
9235         return;
9236     }
9237
9238     /* Set special modes for chess engines.  Later something general
9239      *  could be added here; for now there is just one kludge feature,
9240      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9241      *  when "xboard" is given as an interactive command.
9242      */
9243     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9244         cps->useSigint = FALSE;
9245         cps->useSigterm = FALSE;
9246     }
9247     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9248       ParseFeatures(message+8, cps); if(tryNr < 3) tryNr = 3;
9249       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9250     }
9251
9252     if (!strncmp(message, "setup ", 6) && 
9253         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9254           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9255                                         ) { // [HGM] allow first engine to define opening position
9256       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9257       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9258       *buf = NULLCHAR;
9259       if(sscanf(message, "setup (%s", buf) == 1) {
9260         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9261         ASSIGN(appData.pieceToCharTable, buf);
9262       }
9263       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9264       if(dummy >= 3) {
9265         while(message[s] && message[s++] != ' ');
9266         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9267            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9268 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9269             if(hand > h) handSize = hand; else handSize = h;
9270             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9271             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9272           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9273           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9274           startedFromSetupPosition = FALSE;
9275         }
9276       }
9277       if(startedFromSetupPosition) return;
9278       ParseFEN(boards[0], &dummy, message+s, FALSE);
9279       DrawPosition(TRUE, boards[0]);
9280       CopyBoard(initialPosition, boards[0]);
9281       startedFromSetupPosition = TRUE;
9282       return;
9283     }
9284     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9285       ChessSquare piece = WhitePawn;
9286       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9287       if(*p == '+') promoted++, ID = *++p;
9288       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9289       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9290       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9291       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9292       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9293       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9294       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9295       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9296                                                && gameInfo.variant != VariantGreat
9297                                                && gameInfo.variant != VariantFairy    ) return;
9298       if(piece < EmptySquare) {
9299         pieceDefs = TRUE;
9300         ASSIGN(pieceDesc[piece], buf1);
9301         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9302       }
9303       return;
9304     }
9305     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9306       if(deferChoice) {
9307         LeftClick(Press, 0, 0); // finish the click that was interrupted
9308       } else if(promoSweep != EmptySquare) {
9309         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9310         if(strlen(promoRestrict) > 1) Sweep(0);
9311       }
9312       return;
9313     }
9314     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9315      * want this, I was asked to put it in, and obliged.
9316      */
9317     if (!strncmp(message, "setboard ", 9)) {
9318         Board initial_position;
9319
9320         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9321
9322         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9323             DisplayError(_("Bad FEN received from engine"), 0);
9324             return ;
9325         } else {
9326            Reset(TRUE, FALSE);
9327            CopyBoard(boards[0], initial_position);
9328            initialRulePlies = FENrulePlies;
9329            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9330            else gameMode = MachinePlaysBlack;
9331            DrawPosition(FALSE, boards[currentMove]);
9332         }
9333         return;
9334     }
9335
9336     /*
9337      * Look for communication commands
9338      */
9339     if (!strncmp(message, "telluser ", 9)) {
9340         if(message[9] == '\\' && message[10] == '\\')
9341             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9342         PlayTellSound();
9343         DisplayNote(message + 9);
9344         return;
9345     }
9346     if (!strncmp(message, "tellusererror ", 14)) {
9347         cps->userError = 1;
9348         if(message[14] == '\\' && message[15] == '\\')
9349             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9350         PlayTellSound();
9351         DisplayError(message + 14, 0);
9352         return;
9353     }
9354     if (!strncmp(message, "tellopponent ", 13)) {
9355       if (appData.icsActive) {
9356         if (loggedOn) {
9357           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9358           SendToICS(buf1);
9359         }
9360       } else {
9361         DisplayNote(message + 13);
9362       }
9363       return;
9364     }
9365     if (!strncmp(message, "tellothers ", 11)) {
9366       if (appData.icsActive) {
9367         if (loggedOn) {
9368           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9369           SendToICS(buf1);
9370         }
9371       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9372       return;
9373     }
9374     if (!strncmp(message, "tellall ", 8)) {
9375       if (appData.icsActive) {
9376         if (loggedOn) {
9377           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9378           SendToICS(buf1);
9379         }
9380       } else {
9381         DisplayNote(message + 8);
9382       }
9383       return;
9384     }
9385     if (strncmp(message, "warning", 7) == 0) {
9386         /* Undocumented feature, use tellusererror in new code */
9387         DisplayError(message, 0);
9388         return;
9389     }
9390     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9391         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9392         strcat(realname, " query");
9393         AskQuestion(realname, buf2, buf1, cps->pr);
9394         return;
9395     }
9396     /* Commands from the engine directly to ICS.  We don't allow these to be
9397      *  sent until we are logged on. Crafty kibitzes have been known to
9398      *  interfere with the login process.
9399      */
9400     if (loggedOn) {
9401         if (!strncmp(message, "tellics ", 8)) {
9402             SendToICS(message + 8);
9403             SendToICS("\n");
9404             return;
9405         }
9406         if (!strncmp(message, "tellicsnoalias ", 15)) {
9407             SendToICS(ics_prefix);
9408             SendToICS(message + 15);
9409             SendToICS("\n");
9410             return;
9411         }
9412         /* The following are for backward compatibility only */
9413         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9414             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9415             SendToICS(ics_prefix);
9416             SendToICS(message);
9417             SendToICS("\n");
9418             return;
9419         }
9420     }
9421     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9422         if(initPing == cps->lastPong) {
9423             if(gameInfo.variant == VariantUnknown) {
9424                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9425                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9426                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9427             }
9428             initPing = -1;
9429         }
9430         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9431             abortEngineThink = FALSE;
9432             DisplayMessage("", "");
9433             ThawUI();
9434         }
9435         return;
9436     }
9437     if(!strncmp(message, "highlight ", 10)) {
9438         if(appData.testLegality && !*engineVariant && appData.markers) return;
9439         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9440         return;
9441     }
9442     if(!strncmp(message, "click ", 6)) {
9443         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9444         if(appData.testLegality || !appData.oneClick) return;
9445         sscanf(message+6, "%c%d%c", &f, &y, &c);
9446         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9447         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9448         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9449         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9450         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9451         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9452             LeftClick(Release, lastLeftX, lastLeftY);
9453         controlKey  = (c == ',');
9454         LeftClick(Press, x, y);
9455         LeftClick(Release, x, y);
9456         first.highlight = f;
9457         return;
9458     }
9459     if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9460         int nr = (cps == &second);
9461         appData.isUCI[nr] = isUCI = 1;
9462         ReplaceEngine(cps, nr); // retry install as UCI
9463         return;
9464     }
9465     /*
9466      * If the move is illegal, cancel it and redraw the board.
9467      * Also deal with other error cases.  Matching is rather loose
9468      * here to accommodate engines written before the spec.
9469      */
9470     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9471         strncmp(message, "Error", 5) == 0) {
9472         if (StrStr(message, "name") ||
9473             StrStr(message, "rating") || StrStr(message, "?") ||
9474             StrStr(message, "result") || StrStr(message, "board") ||
9475             StrStr(message, "bk") || StrStr(message, "computer") ||
9476             StrStr(message, "variant") || StrStr(message, "hint") ||
9477             StrStr(message, "random") || StrStr(message, "depth") ||
9478             StrStr(message, "accepted")) {
9479             return;
9480         }
9481         if (StrStr(message, "protover")) {
9482           /* Program is responding to input, so it's apparently done
9483              initializing, and this error message indicates it is
9484              protocol version 1.  So we don't need to wait any longer
9485              for it to initialize and send feature commands. */
9486           FeatureDone(cps, 1);
9487           cps->protocolVersion = 1;
9488           return;
9489         }
9490         cps->maybeThinking = FALSE;
9491
9492         if (StrStr(message, "draw")) {
9493             /* Program doesn't have "draw" command */
9494             cps->sendDrawOffers = 0;
9495             return;
9496         }
9497         if (cps->sendTime != 1 &&
9498             (StrStr(message, "time") || StrStr(message, "otim"))) {
9499           /* Program apparently doesn't have "time" or "otim" command */
9500           cps->sendTime = 0;
9501           return;
9502         }
9503         if (StrStr(message, "analyze")) {
9504             cps->analysisSupport = FALSE;
9505             cps->analyzing = FALSE;
9506 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9507             EditGameEvent(); // [HGM] try to preserve loaded game
9508             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9509             DisplayError(buf2, 0);
9510             return;
9511         }
9512         if (StrStr(message, "(no matching move)st")) {
9513           /* Special kludge for GNU Chess 4 only */
9514           cps->stKludge = TRUE;
9515           SendTimeControl(cps, movesPerSession, timeControl,
9516                           timeIncrement, appData.searchDepth,
9517                           searchTime);
9518           return;
9519         }
9520         if (StrStr(message, "(no matching move)sd")) {
9521           /* Special kludge for GNU Chess 4 only */
9522           cps->sdKludge = TRUE;
9523           SendTimeControl(cps, movesPerSession, timeControl,
9524                           timeIncrement, appData.searchDepth,
9525                           searchTime);
9526           return;
9527         }
9528         if (!StrStr(message, "llegal")) {
9529             return;
9530         }
9531         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9532             gameMode == IcsIdle) return;
9533         if (forwardMostMove <= backwardMostMove) return;
9534         if (pausing) PauseEvent();
9535       if(appData.forceIllegal) {
9536             // [HGM] illegal: machine refused move; force position after move into it
9537           SendToProgram("force\n", cps);
9538           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9539                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9540                 // when black is to move, while there might be nothing on a2 or black
9541                 // might already have the move. So send the board as if white has the move.
9542                 // But first we must change the stm of the engine, as it refused the last move
9543                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9544                 if(WhiteOnMove(forwardMostMove)) {
9545                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9546                     SendBoard(cps, forwardMostMove); // kludgeless board
9547                 } else {
9548                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9549                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9550                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9551                 }
9552           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9553             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9554                  gameMode == TwoMachinesPlay)
9555               SendToProgram("go\n", cps);
9556             return;
9557       } else
9558         if (gameMode == PlayFromGameFile) {
9559             /* Stop reading this game file */
9560             gameMode = EditGame;
9561             ModeHighlight();
9562         }
9563         /* [HGM] illegal-move claim should forfeit game when Xboard */
9564         /* only passes fully legal moves                            */
9565         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9566             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9567                                 "False illegal-move claim", GE_XBOARD );
9568             return; // do not take back move we tested as valid
9569         }
9570         currentMove = forwardMostMove-1;
9571         DisplayMove(currentMove-1); /* before DisplayMoveError */
9572         SwitchClocks(forwardMostMove-1); // [HGM] race
9573         DisplayBothClocks();
9574         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9575                 parseList[currentMove], _(cps->which));
9576         DisplayMoveError(buf1);
9577         DrawPosition(FALSE, boards[currentMove]);
9578
9579         SetUserThinkingEnables();
9580         return;
9581     }
9582     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9583         /* Program has a broken "time" command that
9584            outputs a string not ending in newline.
9585            Don't use it. */
9586         cps->sendTime = 0;
9587     }
9588     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9589         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9590             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9591     }
9592
9593     /*
9594      * If chess program startup fails, exit with an error message.
9595      * Attempts to recover here are futile. [HGM] Well, we try anyway
9596      */
9597     if ((StrStr(message, "unknown host") != NULL)
9598         || (StrStr(message, "No remote directory") != NULL)
9599         || (StrStr(message, "not found") != NULL)
9600         || (StrStr(message, "No such file") != NULL)
9601         || (StrStr(message, "can't alloc") != NULL)
9602         || (StrStr(message, "Permission denied") != NULL)) {
9603
9604         cps->maybeThinking = FALSE;
9605         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9606                 _(cps->which), cps->program, cps->host, message);
9607         RemoveInputSource(cps->isr);
9608         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9609             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9610             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9611         }
9612         return;
9613     }
9614
9615     /*
9616      * Look for hint output
9617      */
9618     if (sscanf(message, "Hint: %s", buf1) == 1) {
9619         if (cps == &first && hintRequested) {
9620             hintRequested = FALSE;
9621             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9622                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9623                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9624                                     PosFlags(forwardMostMove),
9625                                     fromY, fromX, toY, toX, promoChar, buf1);
9626                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9627                 DisplayInformation(buf2);
9628             } else {
9629                 /* Hint move could not be parsed!? */
9630               snprintf(buf2, sizeof(buf2),
9631                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9632                         buf1, _(cps->which));
9633                 DisplayError(buf2, 0);
9634             }
9635         } else {
9636           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9637         }
9638         return;
9639     }
9640
9641     /*
9642      * Ignore other messages if game is not in progress
9643      */
9644     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9645         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9646
9647     /*
9648      * look for win, lose, draw, or draw offer
9649      */
9650     if (strncmp(message, "1-0", 3) == 0) {
9651         char *p, *q, *r = "";
9652         p = strchr(message, '{');
9653         if (p) {
9654             q = strchr(p, '}');
9655             if (q) {
9656                 *q = NULLCHAR;
9657                 r = p + 1;
9658             }
9659         }
9660         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9661         return;
9662     } else if (strncmp(message, "0-1", 3) == 0) {
9663         char *p, *q, *r = "";
9664         p = strchr(message, '{');
9665         if (p) {
9666             q = strchr(p, '}');
9667             if (q) {
9668                 *q = NULLCHAR;
9669                 r = p + 1;
9670             }
9671         }
9672         /* Kludge for Arasan 4.1 bug */
9673         if (strcmp(r, "Black resigns") == 0) {
9674             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9675             return;
9676         }
9677         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9678         return;
9679     } else if (strncmp(message, "1/2", 3) == 0) {
9680         char *p, *q, *r = "";
9681         p = strchr(message, '{');
9682         if (p) {
9683             q = strchr(p, '}');
9684             if (q) {
9685                 *q = NULLCHAR;
9686                 r = p + 1;
9687             }
9688         }
9689
9690         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9691         return;
9692
9693     } else if (strncmp(message, "White resign", 12) == 0) {
9694         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9695         return;
9696     } else if (strncmp(message, "Black resign", 12) == 0) {
9697         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9698         return;
9699     } else if (strncmp(message, "White matches", 13) == 0 ||
9700                strncmp(message, "Black matches", 13) == 0   ) {
9701         /* [HGM] ignore GNUShogi noises */
9702         return;
9703     } else if (strncmp(message, "White", 5) == 0 &&
9704                message[5] != '(' &&
9705                StrStr(message, "Black") == NULL) {
9706         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9707         return;
9708     } else if (strncmp(message, "Black", 5) == 0 &&
9709                message[5] != '(') {
9710         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9711         return;
9712     } else if (strcmp(message, "resign") == 0 ||
9713                strcmp(message, "computer resigns") == 0) {
9714         switch (gameMode) {
9715           case MachinePlaysBlack:
9716           case IcsPlayingBlack:
9717             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9718             break;
9719           case MachinePlaysWhite:
9720           case IcsPlayingWhite:
9721             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9722             break;
9723           case TwoMachinesPlay:
9724             if (cps->twoMachinesColor[0] == 'w')
9725               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9726             else
9727               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9728             break;
9729           default:
9730             /* can't happen */
9731             break;
9732         }
9733         return;
9734     } else if (strncmp(message, "opponent mates", 14) == 0) {
9735         switch (gameMode) {
9736           case MachinePlaysBlack:
9737           case IcsPlayingBlack:
9738             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9739             break;
9740           case MachinePlaysWhite:
9741           case IcsPlayingWhite:
9742             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9743             break;
9744           case TwoMachinesPlay:
9745             if (cps->twoMachinesColor[0] == 'w')
9746               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9747             else
9748               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9749             break;
9750           default:
9751             /* can't happen */
9752             break;
9753         }
9754         return;
9755     } else if (strncmp(message, "computer mates", 14) == 0) {
9756         switch (gameMode) {
9757           case MachinePlaysBlack:
9758           case IcsPlayingBlack:
9759             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9760             break;
9761           case MachinePlaysWhite:
9762           case IcsPlayingWhite:
9763             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9764             break;
9765           case TwoMachinesPlay:
9766             if (cps->twoMachinesColor[0] == 'w')
9767               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9768             else
9769               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9770             break;
9771           default:
9772             /* can't happen */
9773             break;
9774         }
9775         return;
9776     } else if (strncmp(message, "checkmate", 9) == 0) {
9777         if (WhiteOnMove(forwardMostMove)) {
9778             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9779         } else {
9780             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9781         }
9782         return;
9783     } else if (strstr(message, "Draw") != NULL ||
9784                strstr(message, "game is a draw") != NULL) {
9785         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9786         return;
9787     } else if (strstr(message, "offer") != NULL &&
9788                strstr(message, "draw") != NULL) {
9789 #if ZIPPY
9790         if (appData.zippyPlay && first.initDone) {
9791             /* Relay offer to ICS */
9792             SendToICS(ics_prefix);
9793             SendToICS("draw\n");
9794         }
9795 #endif
9796         cps->offeredDraw = 2; /* valid until this engine moves twice */
9797         if (gameMode == TwoMachinesPlay) {
9798             if (cps->other->offeredDraw) {
9799                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9800             /* [HGM] in two-machine mode we delay relaying draw offer      */
9801             /* until after we also have move, to see if it is really claim */
9802             }
9803         } else if (gameMode == MachinePlaysWhite ||
9804                    gameMode == MachinePlaysBlack) {
9805           if (userOfferedDraw) {
9806             DisplayInformation(_("Machine accepts your draw offer"));
9807             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9808           } else {
9809             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9810           }
9811         }
9812     }
9813
9814
9815     /*
9816      * Look for thinking output
9817      */
9818     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9819           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9820                                 ) {
9821         int plylev, mvleft, mvtot, curscore, time;
9822         char mvname[MOVE_LEN];
9823         u64 nodes; // [DM]
9824         char plyext;
9825         int ignore = FALSE;
9826         int prefixHint = FALSE;
9827         mvname[0] = NULLCHAR;
9828
9829         switch (gameMode) {
9830           case MachinePlaysBlack:
9831           case IcsPlayingBlack:
9832             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9833             break;
9834           case MachinePlaysWhite:
9835           case IcsPlayingWhite:
9836             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9837             break;
9838           case AnalyzeMode:
9839           case AnalyzeFile:
9840             break;
9841           case IcsObserving: /* [DM] icsEngineAnalyze */
9842             if (!appData.icsEngineAnalyze) ignore = TRUE;
9843             break;
9844           case TwoMachinesPlay:
9845             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9846                 ignore = TRUE;
9847             }
9848             break;
9849           default:
9850             ignore = TRUE;
9851             break;
9852         }
9853
9854         if (!ignore) {
9855             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9856             int solved = 0;
9857             buf1[0] = NULLCHAR;
9858             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9859                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9860                 char score_buf[MSG_SIZ];
9861
9862                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9863                     nodes += u64Const(0x100000000);
9864
9865                 if (plyext != ' ' && plyext != '\t') {
9866                     time *= 100;
9867                 }
9868
9869                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9870                 if( cps->scoreIsAbsolute &&
9871                     ( gameMode == MachinePlaysBlack ||
9872                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9873                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9874                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9875                      !WhiteOnMove(currentMove)
9876                     ) )
9877                 {
9878                     curscore = -curscore;
9879                 }
9880
9881                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9882
9883                 if(*bestMove) { // rememer time best EPD move was first found
9884                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9885                     ChessMove mt; char *p = bestMove;
9886                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9887                     solved = 0;
9888                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9889                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9890                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9891                             solved = 1;
9892                             break;
9893                         }
9894                         while(*p && *p != ' ') p++;
9895                         while(*p == ' ') p++;
9896                     }
9897                     if(!solved) solvingTime = -1;
9898                 }
9899                 if(*avoidMove && !solved) {
9900                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9901                     ChessMove mt; char *p = avoidMove, solved = 1;
9902                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9903                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9904                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9905                             solved = 0; solvingTime = -2;
9906                             break;
9907                         }
9908                         while(*p && *p != ' ') p++;
9909                         while(*p == ' ') p++;
9910                     }
9911                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9912                 }
9913
9914                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9915                         char buf[MSG_SIZ];
9916                         FILE *f;
9917                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9918                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9919                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9920                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9921                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9922                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9923                                 fclose(f);
9924                         }
9925                         else
9926                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9927                           DisplayError(_("failed writing PV"), 0);
9928                 }
9929
9930                 tempStats.depth = plylev;
9931                 tempStats.nodes = nodes;
9932                 tempStats.time = time;
9933                 tempStats.score = curscore;
9934                 tempStats.got_only_move = 0;
9935
9936                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9937                         int ticklen;
9938
9939                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9940                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9941                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9942                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9943                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9944                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9945                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9946                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9947                 }
9948
9949                 /* Buffer overflow protection */
9950                 if (pv[0] != NULLCHAR) {
9951                     if (strlen(pv) >= sizeof(tempStats.movelist)
9952                         && appData.debugMode) {
9953                         fprintf(debugFP,
9954                                 "PV is too long; using the first %u bytes.\n",
9955                                 (unsigned) sizeof(tempStats.movelist) - 1);
9956                     }
9957
9958                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9959                 } else {
9960                     sprintf(tempStats.movelist, " no PV\n");
9961                 }
9962
9963                 if (tempStats.seen_stat) {
9964                     tempStats.ok_to_send = 1;
9965                 }
9966
9967                 if (strchr(tempStats.movelist, '(') != NULL) {
9968                     tempStats.line_is_book = 1;
9969                     tempStats.nr_moves = 0;
9970                     tempStats.moves_left = 0;
9971                 } else {
9972                     tempStats.line_is_book = 0;
9973                 }
9974
9975                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9976                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9977
9978                 SendProgramStatsToFrontend( cps, &tempStats );
9979
9980                 /*
9981                     [AS] Protect the thinkOutput buffer from overflow... this
9982                     is only useful if buf1 hasn't overflowed first!
9983                 */
9984                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9985                 if(curscore >= MATE_SCORE) 
9986                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9987                 else if(curscore <= -MATE_SCORE) 
9988                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9989                 else
9990                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9991                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9992                          plylev,
9993                          (gameMode == TwoMachinesPlay ?
9994                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9995                          score_buf,
9996                          prefixHint ? lastHint : "",
9997                          prefixHint ? " " : "" );
9998
9999                 if( buf1[0] != NULLCHAR ) {
10000                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
10001
10002                     if( strlen(pv) > max_len ) {
10003                         if( appData.debugMode) {
10004                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
10005                         }
10006                         pv[max_len+1] = '\0';
10007                     }
10008
10009                     strcat( thinkOutput, pv);
10010                 }
10011
10012                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10013                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10014                     DisplayMove(currentMove - 1);
10015                 }
10016                 return;
10017
10018             } else if ((p=StrStr(message, "(only move)")) != NULL) {
10019                 /* crafty (9.25+) says "(only move) <move>"
10020                  * if there is only 1 legal move
10021                  */
10022                 sscanf(p, "(only move) %s", buf1);
10023                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10024                 sprintf(programStats.movelist, "%s (only move)", buf1);
10025                 programStats.depth = 1;
10026                 programStats.nr_moves = 1;
10027                 programStats.moves_left = 1;
10028                 programStats.nodes = 1;
10029                 programStats.time = 1;
10030                 programStats.got_only_move = 1;
10031
10032                 /* Not really, but we also use this member to
10033                    mean "line isn't going to change" (Crafty
10034                    isn't searching, so stats won't change) */
10035                 programStats.line_is_book = 1;
10036
10037                 SendProgramStatsToFrontend( cps, &programStats );
10038
10039                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10040                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10041                     DisplayMove(currentMove - 1);
10042                 }
10043                 return;
10044             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10045                               &time, &nodes, &plylev, &mvleft,
10046                               &mvtot, mvname) >= 5) {
10047                 /* The stat01: line is from Crafty (9.29+) in response
10048                    to the "." command */
10049                 programStats.seen_stat = 1;
10050                 cps->maybeThinking = TRUE;
10051
10052                 if (programStats.got_only_move || !appData.periodicUpdates)
10053                   return;
10054
10055                 programStats.depth = plylev;
10056                 programStats.time = time;
10057                 programStats.nodes = nodes;
10058                 programStats.moves_left = mvleft;
10059                 programStats.nr_moves = mvtot;
10060                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10061                 programStats.ok_to_send = 1;
10062                 programStats.movelist[0] = '\0';
10063
10064                 SendProgramStatsToFrontend( cps, &programStats );
10065
10066                 return;
10067
10068             } else if (strncmp(message,"++",2) == 0) {
10069                 /* Crafty 9.29+ outputs this */
10070                 programStats.got_fail = 2;
10071                 return;
10072
10073             } else if (strncmp(message,"--",2) == 0) {
10074                 /* Crafty 9.29+ outputs this */
10075                 programStats.got_fail = 1;
10076                 return;
10077
10078             } else if (thinkOutput[0] != NULLCHAR &&
10079                        strncmp(message, "    ", 4) == 0) {
10080                 unsigned message_len;
10081
10082                 p = message;
10083                 while (*p && *p == ' ') p++;
10084
10085                 message_len = strlen( p );
10086
10087                 /* [AS] Avoid buffer overflow */
10088                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10089                     strcat(thinkOutput, " ");
10090                     strcat(thinkOutput, p);
10091                 }
10092
10093                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10094                     strcat(programStats.movelist, " ");
10095                     strcat(programStats.movelist, p);
10096                 }
10097
10098                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10099                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10100                     DisplayMove(currentMove - 1);
10101                 }
10102                 return;
10103             }
10104         }
10105         else {
10106             buf1[0] = NULLCHAR;
10107
10108             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10109                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10110             {
10111                 ChessProgramStats cpstats;
10112
10113                 if (plyext != ' ' && plyext != '\t') {
10114                     time *= 100;
10115                 }
10116
10117                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10118                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10119                     curscore = -curscore;
10120                 }
10121
10122                 cpstats.depth = plylev;
10123                 cpstats.nodes = nodes;
10124                 cpstats.time = time;
10125                 cpstats.score = curscore;
10126                 cpstats.got_only_move = 0;
10127                 cpstats.movelist[0] = '\0';
10128
10129                 if (buf1[0] != NULLCHAR) {
10130                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10131                 }
10132
10133                 cpstats.ok_to_send = 0;
10134                 cpstats.line_is_book = 0;
10135                 cpstats.nr_moves = 0;
10136                 cpstats.moves_left = 0;
10137
10138                 SendProgramStatsToFrontend( cps, &cpstats );
10139             }
10140         }
10141     }
10142 }
10143
10144
10145 /* Parse a game score from the character string "game", and
10146    record it as the history of the current game.  The game
10147    score is NOT assumed to start from the standard position.
10148    The display is not updated in any way.
10149    */
10150 void
10151 ParseGameHistory (char *game)
10152 {
10153     ChessMove moveType;
10154     int fromX, fromY, toX, toY, boardIndex, mask;
10155     char promoChar;
10156     char *p, *q;
10157     char buf[MSG_SIZ];
10158
10159     if (appData.debugMode)
10160       fprintf(debugFP, "Parsing game history: %s\n", game);
10161
10162     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10163     gameInfo.site = StrSave(appData.icsHost);
10164     gameInfo.date = PGNDate();
10165     gameInfo.round = StrSave("-");
10166
10167     /* Parse out names of players */
10168     while (*game == ' ') game++;
10169     p = buf;
10170     while (*game != ' ') *p++ = *game++;
10171     *p = NULLCHAR;
10172     gameInfo.white = StrSave(buf);
10173     while (*game == ' ') game++;
10174     p = buf;
10175     while (*game != ' ' && *game != '\n') *p++ = *game++;
10176     *p = NULLCHAR;
10177     gameInfo.black = StrSave(buf);
10178
10179     /* Parse moves */
10180     boardIndex = blackPlaysFirst ? 1 : 0;
10181     yynewstr(game);
10182     for (;;) {
10183         yyboardindex = boardIndex;
10184         moveType = (ChessMove) Myylex();
10185         switch (moveType) {
10186           case IllegalMove:             /* maybe suicide chess, etc. */
10187   if (appData.debugMode) {
10188     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10189     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10190     setbuf(debugFP, NULL);
10191   }
10192           case WhitePromotion:
10193           case BlackPromotion:
10194           case WhiteNonPromotion:
10195           case BlackNonPromotion:
10196           case NormalMove:
10197           case FirstLeg:
10198           case WhiteCapturesEnPassant:
10199           case BlackCapturesEnPassant:
10200           case WhiteKingSideCastle:
10201           case WhiteQueenSideCastle:
10202           case BlackKingSideCastle:
10203           case BlackQueenSideCastle:
10204           case WhiteKingSideCastleWild:
10205           case WhiteQueenSideCastleWild:
10206           case BlackKingSideCastleWild:
10207           case BlackQueenSideCastleWild:
10208           /* PUSH Fabien */
10209           case WhiteHSideCastleFR:
10210           case WhiteASideCastleFR:
10211           case BlackHSideCastleFR:
10212           case BlackASideCastleFR:
10213           /* POP Fabien */
10214             fromX = currentMoveString[0] - AAA;
10215             fromY = currentMoveString[1] - ONE;
10216             toX = currentMoveString[2] - AAA;
10217             toY = currentMoveString[3] - ONE;
10218             promoChar = currentMoveString[4];
10219             break;
10220           case WhiteDrop:
10221           case BlackDrop:
10222             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10223             fromX = moveType == WhiteDrop ?
10224               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10225             (int) CharToPiece(ToLower(currentMoveString[0]));
10226             fromY = DROP_RANK;
10227             toX = currentMoveString[2] - AAA;
10228             toY = currentMoveString[3] - ONE;
10229             promoChar = NULLCHAR;
10230             break;
10231           case AmbiguousMove:
10232             /* bug? */
10233             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10234   if (appData.debugMode) {
10235     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10236     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10237     setbuf(debugFP, NULL);
10238   }
10239             DisplayError(buf, 0);
10240             return;
10241           case ImpossibleMove:
10242             /* bug? */
10243             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10244   if (appData.debugMode) {
10245     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10247     setbuf(debugFP, NULL);
10248   }
10249             DisplayError(buf, 0);
10250             return;
10251           case EndOfFile:
10252             if (boardIndex < backwardMostMove) {
10253                 /* Oops, gap.  How did that happen? */
10254                 DisplayError(_("Gap in move list"), 0);
10255                 return;
10256             }
10257             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10258             if (boardIndex > forwardMostMove) {
10259                 forwardMostMove = boardIndex;
10260             }
10261             return;
10262           case ElapsedTime:
10263             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10264                 strcat(parseList[boardIndex-1], " ");
10265                 strcat(parseList[boardIndex-1], yy_text);
10266             }
10267             continue;
10268           case Comment:
10269           case PGNTag:
10270           case NAG:
10271           default:
10272             /* ignore */
10273             continue;
10274           case WhiteWins:
10275           case BlackWins:
10276           case GameIsDrawn:
10277           case GameUnfinished:
10278             if (gameMode == IcsExamining) {
10279                 if (boardIndex < backwardMostMove) {
10280                     /* Oops, gap.  How did that happen? */
10281                     return;
10282                 }
10283                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10284                 return;
10285             }
10286             gameInfo.result = moveType;
10287             p = strchr(yy_text, '{');
10288             if (p == NULL) p = strchr(yy_text, '(');
10289             if (p == NULL) {
10290                 p = yy_text;
10291                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10292             } else {
10293                 q = strchr(p, *p == '{' ? '}' : ')');
10294                 if (q != NULL) *q = NULLCHAR;
10295                 p++;
10296             }
10297             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10298             gameInfo.resultDetails = StrSave(p);
10299             continue;
10300         }
10301         if (boardIndex >= forwardMostMove &&
10302             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10303             backwardMostMove = blackPlaysFirst ? 1 : 0;
10304             return;
10305         }
10306         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10307                                  fromY, fromX, toY, toX, promoChar,
10308                                  parseList[boardIndex]);
10309         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10310         /* currentMoveString is set as a side-effect of yylex */
10311         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10312         strcat(moveList[boardIndex], "\n");
10313         boardIndex++;
10314         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10315         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10316         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10317           case MT_NONE:
10318           case MT_STALEMATE:
10319           default:
10320             break;
10321           case MT_CHECK:
10322             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10323             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10324                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10325                 break;
10326             }
10327           case MT_CHECKMATE:
10328           case MT_STAINMATE:
10329             strcat(parseList[boardIndex - 1], "#");
10330             break;
10331         }
10332     }
10333 }
10334
10335
10336 /* Apply a move to the given board  */
10337 void
10338 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10339 {
10340   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10341   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10342
10343     /* [HGM] compute & store e.p. status and castling rights for new position */
10344     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10345
10346       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10347       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10348       board[EP_STATUS] = EP_NONE;
10349       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10350
10351   if (fromY == DROP_RANK) {
10352         /* must be first */
10353         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10354             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10355             return;
10356         }
10357         piece = board[toY][toX] = (ChessSquare) fromX;
10358   } else {
10359 //      ChessSquare victim;
10360       int i;
10361
10362       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10363 //           victim = board[killY][killX],
10364            killed = board[killY][killX],
10365            board[killY][killX] = EmptySquare,
10366            board[EP_STATUS] = EP_CAPTURE;
10367            if( kill2X >= 0 && kill2Y >= 0)
10368              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10369       }
10370
10371       if( board[toY][toX] != EmptySquare ) {
10372            board[EP_STATUS] = EP_CAPTURE;
10373            if( (fromX != toX || fromY != toY) && // not igui!
10374                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10375                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10376                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10377            }
10378       }
10379
10380       pawn = board[fromY][fromX];
10381       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10382         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10383             captured = board[lastRank][lastFile]; // remove victim
10384             board[lastRank][lastFile] = EmptySquare;
10385             pawn = EmptySquare; // kludge to suppress old e.p. code
10386         }
10387       }
10388       if( pawn == WhiteLance || pawn == BlackLance ) {
10389            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10390                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10391                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10392            }
10393       }
10394       if( pawn == WhitePawn ) {
10395            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10396                board[EP_STATUS] = EP_PAWN_MOVE;
10397            if( toY-fromY>=2) {
10398                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10399                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10400                         gameInfo.variant != VariantBerolina || toX < fromX)
10401                       board[EP_STATUS] = toX | berolina;
10402                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10403                         gameInfo.variant != VariantBerolina || toX > fromX)
10404                       board[EP_STATUS] = toX;
10405                board[LAST_TO] = toX + 256*toY;
10406            }
10407       } else
10408       if( pawn == BlackPawn ) {
10409            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10410                board[EP_STATUS] = EP_PAWN_MOVE;
10411            if( toY-fromY<= -2) {
10412                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10413                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10414                         gameInfo.variant != VariantBerolina || toX < fromX)
10415                       board[EP_STATUS] = toX | berolina;
10416                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10417                         gameInfo.variant != VariantBerolina || toX > fromX)
10418                       board[EP_STATUS] = toX;
10419                board[LAST_TO] = toX + 256*toY;
10420            }
10421        }
10422
10423        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10424        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10425        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10426        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10427
10428        for(i=0; i<nrCastlingRights; i++) {
10429            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10430               board[CASTLING][i] == toX   && castlingRank[i] == toY
10431              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10432        }
10433
10434        if(gameInfo.variant == VariantSChess) { // update virginity
10435            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10436            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10437            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10438            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10439        }
10440
10441      if (fromX == toX && fromY == toY && killX < 0) return;
10442
10443      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10444      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10445      if(gameInfo.variant == VariantKnightmate)
10446          king += (int) WhiteUnicorn - (int) WhiteKing;
10447
10448     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10449        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10450         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10451         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10452         board[EP_STATUS] = EP_NONE; // capture was fake!
10453     } else
10454     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10455         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10456         board[toY][toX] = piece;
10457         board[EP_STATUS] = EP_NONE; // capture was fake!
10458     } else
10459     /* Code added by Tord: */
10460     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10461     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10462         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10463       board[EP_STATUS] = EP_NONE; // capture was fake!
10464       board[fromY][fromX] = EmptySquare;
10465       board[toY][toX] = EmptySquare;
10466       if((toX > fromX) != (piece == WhiteRook)) {
10467         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10468       } else {
10469         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10470       }
10471     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10472                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10473       board[EP_STATUS] = EP_NONE;
10474       board[fromY][fromX] = EmptySquare;
10475       board[toY][toX] = EmptySquare;
10476       if((toX > fromX) != (piece == BlackRook)) {
10477         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10478       } else {
10479         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10480       }
10481     /* End of code added by Tord */
10482
10483     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10484         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10485         board[toY][toX] = piece;
10486     } else if (board[fromY][fromX] == king
10487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10488         && toY == fromY && toX > fromX+1) {
10489         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10490                                                                                              ; // castle with nearest piece
10491         board[fromY][toX-1] = board[fromY][rookX];
10492         board[fromY][rookX] = EmptySquare;
10493         board[fromY][fromX] = EmptySquare;
10494         board[toY][toX] = king;
10495     } else if (board[fromY][fromX] == king
10496         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10497                && toY == fromY && toX < fromX-1) {
10498         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10499                                                                                   ; // castle with nearest piece
10500         board[fromY][toX+1] = board[fromY][rookX];
10501         board[fromY][rookX] = EmptySquare;
10502         board[fromY][fromX] = EmptySquare;
10503         board[toY][toX] = king;
10504     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10505                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10506                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10507                ) {
10508         /* white pawn promotion */
10509         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10510         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10511             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10512         board[fromY][fromX] = EmptySquare;
10513     } else if ((fromY >= BOARD_HEIGHT>>1)
10514                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10515                && (toX != fromX)
10516                && gameInfo.variant != VariantXiangqi
10517                && gameInfo.variant != VariantBerolina
10518                && (pawn == WhitePawn)
10519                && (board[toY][toX] == EmptySquare)) {
10520         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10521         board[fromY][fromX] = EmptySquare;
10522         board[toY][toX] = piece;
10523         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10524     } else if ((fromY == BOARD_HEIGHT-4)
10525                && (toX == fromX)
10526                && gameInfo.variant == VariantBerolina
10527                && (board[fromY][fromX] == WhitePawn)
10528                && (board[toY][toX] == EmptySquare)) {
10529         board[fromY][fromX] = EmptySquare;
10530         board[toY][toX] = WhitePawn;
10531         if(oldEP & EP_BEROLIN_A) {
10532                 captured = board[fromY][fromX-1];
10533                 board[fromY][fromX-1] = EmptySquare;
10534         }else{  captured = board[fromY][fromX+1];
10535                 board[fromY][fromX+1] = EmptySquare;
10536         }
10537     } else if (board[fromY][fromX] == king
10538         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10539                && toY == fromY && toX > fromX+1) {
10540         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10541                                                                                              ;
10542         board[fromY][toX-1] = board[fromY][rookX];
10543         board[fromY][rookX] = EmptySquare;
10544         board[fromY][fromX] = EmptySquare;
10545         board[toY][toX] = king;
10546     } else if (board[fromY][fromX] == king
10547         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10548                && toY == fromY && toX < fromX-1) {
10549         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10550                                                                                 ;
10551         board[fromY][toX+1] = board[fromY][rookX];
10552         board[fromY][rookX] = EmptySquare;
10553         board[fromY][fromX] = EmptySquare;
10554         board[toY][toX] = king;
10555     } else if (fromY == 7 && fromX == 3
10556                && board[fromY][fromX] == BlackKing
10557                && toY == 7 && toX == 5) {
10558         board[fromY][fromX] = EmptySquare;
10559         board[toY][toX] = BlackKing;
10560         board[fromY][7] = EmptySquare;
10561         board[toY][4] = BlackRook;
10562     } else if (fromY == 7 && fromX == 3
10563                && board[fromY][fromX] == BlackKing
10564                && toY == 7 && toX == 1) {
10565         board[fromY][fromX] = EmptySquare;
10566         board[toY][toX] = BlackKing;
10567         board[fromY][0] = EmptySquare;
10568         board[toY][2] = BlackRook;
10569     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10570                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10571                && toY < promoRank && promoChar
10572                ) {
10573         /* black pawn promotion */
10574         board[toY][toX] = CharToPiece(ToLower(promoChar));
10575         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10576             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10577         board[fromY][fromX] = EmptySquare;
10578     } else if ((fromY < BOARD_HEIGHT>>1)
10579                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10580                && (toX != fromX)
10581                && gameInfo.variant != VariantXiangqi
10582                && gameInfo.variant != VariantBerolina
10583                && (pawn == BlackPawn)
10584                && (board[toY][toX] == EmptySquare)) {
10585         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10586         board[fromY][fromX] = EmptySquare;
10587         board[toY][toX] = piece;
10588         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10589     } else if ((fromY == 3)
10590                && (toX == fromX)
10591                && gameInfo.variant == VariantBerolina
10592                && (board[fromY][fromX] == BlackPawn)
10593                && (board[toY][toX] == EmptySquare)) {
10594         board[fromY][fromX] = EmptySquare;
10595         board[toY][toX] = BlackPawn;
10596         if(oldEP & EP_BEROLIN_A) {
10597                 captured = board[fromY][fromX-1];
10598                 board[fromY][fromX-1] = EmptySquare;
10599         }else{  captured = board[fromY][fromX+1];
10600                 board[fromY][fromX+1] = EmptySquare;
10601         }
10602     } else {
10603         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10604         board[fromY][fromX] = EmptySquare;
10605         board[toY][toX] = piece;
10606     }
10607   }
10608
10609     if (gameInfo.holdingsWidth != 0) {
10610
10611       /* !!A lot more code needs to be written to support holdings  */
10612       /* [HGM] OK, so I have written it. Holdings are stored in the */
10613       /* penultimate board files, so they are automaticlly stored   */
10614       /* in the game history.                                       */
10615       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10616                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10617         /* Delete from holdings, by decreasing count */
10618         /* and erasing image if necessary            */
10619         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10620         if(p < (int) BlackPawn) { /* white drop */
10621              p -= (int)WhitePawn;
10622                  p = PieceToNumber((ChessSquare)p);
10623              if(p >= gameInfo.holdingsSize) p = 0;
10624              if(--board[p][BOARD_WIDTH-2] <= 0)
10625                   board[p][BOARD_WIDTH-1] = EmptySquare;
10626              if((int)board[p][BOARD_WIDTH-2] < 0)
10627                         board[p][BOARD_WIDTH-2] = 0;
10628         } else {                  /* black drop */
10629              p -= (int)BlackPawn;
10630                  p = PieceToNumber((ChessSquare)p);
10631              if(p >= gameInfo.holdingsSize) p = 0;
10632              if(--board[handSize-1-p][1] <= 0)
10633                   board[handSize-1-p][0] = EmptySquare;
10634              if((int)board[handSize-1-p][1] < 0)
10635                         board[handSize-1-p][1] = 0;
10636         }
10637       }
10638       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10639           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10640         /* [HGM] holdings: Add to holdings, if holdings exist */
10641         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10642                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10643                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10644         }
10645         p = (int) captured;
10646         if (p >= (int) BlackPawn) {
10647           p -= (int)BlackPawn;
10648           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10649                   /* Restore shogi-promoted piece to its original  first */
10650                   captured = (ChessSquare) (DEMOTED(captured));
10651                   p = DEMOTED(p);
10652           }
10653           p = PieceToNumber((ChessSquare)p);
10654           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10655           board[p][BOARD_WIDTH-2]++;
10656           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10657         } else {
10658           p -= (int)WhitePawn;
10659           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10660                   captured = (ChessSquare) (DEMOTED(captured));
10661                   p = DEMOTED(p);
10662           }
10663           p = PieceToNumber((ChessSquare)p);
10664           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10665           board[handSize-1-p][1]++;
10666           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10667         }
10668       }
10669     } else if (gameInfo.variant == VariantAtomic) {
10670       if (captured != EmptySquare) {
10671         int y, x;
10672         for (y = toY-1; y <= toY+1; y++) {
10673           for (x = toX-1; x <= toX+1; x++) {
10674             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10675                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10676               board[y][x] = EmptySquare;
10677             }
10678           }
10679         }
10680         board[toY][toX] = EmptySquare;
10681       }
10682     }
10683
10684     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10685         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10686     } else
10687     if(promoChar == '+') {
10688         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10689         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10690         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10691           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10692     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10693         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10694         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10695            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10696         board[toY][toX] = newPiece;
10697     }
10698     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10699                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10700         // [HGM] superchess: take promotion piece out of holdings
10701         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10702         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10703             if(!--board[k][BOARD_WIDTH-2])
10704                 board[k][BOARD_WIDTH-1] = EmptySquare;
10705         } else {
10706             if(!--board[handSize-1-k][1])
10707                 board[handSize-1-k][0] = EmptySquare;
10708         }
10709     }
10710 }
10711
10712 /* Updates forwardMostMove */
10713 void
10714 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10715 {
10716     int x = toX, y = toY, mask;
10717     char *s = parseList[forwardMostMove];
10718     ChessSquare p = boards[forwardMostMove][toY][toX];
10719 //    forwardMostMove++; // [HGM] bare: moved downstream
10720
10721     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10722     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10723     (void) CoordsToAlgebraic(boards[forwardMostMove],
10724                              PosFlags(forwardMostMove),
10725                              fromY, fromX, y, x, (killX < 0)*promoChar,
10726                              s);
10727     if(kill2X >= 0 && kill2Y >= 0)
10728         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10729     if(killX >= 0 && killY >= 0)
10730         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10731                                            toX + AAA, toY + ONE - '0', promoChar);
10732
10733     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10734         int timeLeft; static int lastLoadFlag=0; int king, piece;
10735         piece = boards[forwardMostMove][fromY][fromX];
10736         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10737         if(gameInfo.variant == VariantKnightmate)
10738             king += (int) WhiteUnicorn - (int) WhiteKing;
10739         if(forwardMostMove == 0) {
10740             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10741                 fprintf(serverMoves, "%s;", UserName());
10742             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10743                 fprintf(serverMoves, "%s;", second.tidy);
10744             fprintf(serverMoves, "%s;", first.tidy);
10745             if(gameMode == MachinePlaysWhite)
10746                 fprintf(serverMoves, "%s;", UserName());
10747             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10748                 fprintf(serverMoves, "%s;", second.tidy);
10749         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10750         lastLoadFlag = loadFlag;
10751         // print base move
10752         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10753         // print castling suffix
10754         if( toY == fromY && piece == king ) {
10755             if(toX-fromX > 1)
10756                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10757             if(fromX-toX >1)
10758                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10759         }
10760         // e.p. suffix
10761         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10762              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10763              boards[forwardMostMove][toY][toX] == EmptySquare
10764              && fromX != toX && fromY != toY)
10765                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10766         // promotion suffix
10767         if(promoChar != NULLCHAR) {
10768             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10769                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10770                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10771             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10772         }
10773         if(!loadFlag) {
10774                 char buf[MOVE_LEN*2], *p; int len;
10775             fprintf(serverMoves, "/%d/%d",
10776                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10777             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10778             else                      timeLeft = blackTimeRemaining/1000;
10779             fprintf(serverMoves, "/%d", timeLeft);
10780                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10781                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10782                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10783                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10784             fprintf(serverMoves, "/%s", buf);
10785         }
10786         fflush(serverMoves);
10787     }
10788
10789     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10790         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10791       return;
10792     }
10793     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10794     if (commentList[forwardMostMove+1] != NULL) {
10795         free(commentList[forwardMostMove+1]);
10796         commentList[forwardMostMove+1] = NULL;
10797     }
10798     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10799     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10800     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10801     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10802     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10803     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10804     adjustedClock = FALSE;
10805     gameInfo.result = GameUnfinished;
10806     if (gameInfo.resultDetails != NULL) {
10807         free(gameInfo.resultDetails);
10808         gameInfo.resultDetails = NULL;
10809     }
10810     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10811                               moveList[forwardMostMove - 1]);
10812     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10813     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10814       case MT_NONE:
10815       case MT_STALEMATE:
10816       default:
10817         break;
10818       case MT_CHECK:
10819         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10820         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10821             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10822             break;
10823         }
10824       case MT_CHECKMATE:
10825       case MT_STAINMATE:
10826         strcat(parseList[forwardMostMove - 1], "#");
10827         break;
10828     }
10829 }
10830
10831 /* Updates currentMove if not pausing */
10832 void
10833 ShowMove (int fromX, int fromY, int toX, int toY)
10834 {
10835     int instant = (gameMode == PlayFromGameFile) ?
10836         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10837     if(appData.noGUI) return;
10838     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10839         if (!instant) {
10840             if (forwardMostMove == currentMove + 1) {
10841                 AnimateMove(boards[forwardMostMove - 1],
10842                             fromX, fromY, toX, toY);
10843             }
10844         }
10845         currentMove = forwardMostMove;
10846     }
10847
10848     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10849
10850     if (instant) return;
10851
10852     DisplayMove(currentMove - 1);
10853     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10854             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10855                 SetHighlights(fromX, fromY, toX, toY);
10856             }
10857     }
10858     DrawPosition(FALSE, boards[currentMove]);
10859     DisplayBothClocks();
10860     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10861 }
10862
10863 void
10864 SendEgtPath (ChessProgramState *cps)
10865 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10866         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10867
10868         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10869
10870         while(*p) {
10871             char c, *q = name+1, *r, *s;
10872
10873             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10874             while(*p && *p != ',') *q++ = *p++;
10875             *q++ = ':'; *q = 0;
10876             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10877                 strcmp(name, ",nalimov:") == 0 ) {
10878                 // take nalimov path from the menu-changeable option first, if it is defined
10879               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10880                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10881             } else
10882             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10883                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10884                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10885                 s = r = StrStr(s, ":") + 1; // beginning of path info
10886                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10887                 c = *r; *r = 0;             // temporarily null-terminate path info
10888                     *--q = 0;               // strip of trailig ':' from name
10889                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10890                 *r = c;
10891                 SendToProgram(buf,cps);     // send egtbpath command for this format
10892             }
10893             if(*p == ',') p++; // read away comma to position for next format name
10894         }
10895 }
10896
10897 static int
10898 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10899 {
10900       int width = 8, height = 8, holdings = 0;             // most common sizes
10901       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10902       // correct the deviations default for each variant
10903       if( v == VariantXiangqi ) width = 9,  height = 10;
10904       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10905       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10906       if( v == VariantCapablanca || v == VariantCapaRandom ||
10907           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10908                                 width = 10;
10909       if( v == VariantCourier ) width = 12;
10910       if( v == VariantSuper )                            holdings = 8;
10911       if( v == VariantGreat )   width = 10,              holdings = 8;
10912       if( v == VariantSChess )                           holdings = 7;
10913       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10914       if( v == VariantChuChess) width = 10, height = 10;
10915       if( v == VariantChu )     width = 12, height = 12;
10916       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10917              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10918              holdingsSize >= 0 && holdingsSize != holdings;
10919 }
10920
10921 char variantError[MSG_SIZ];
10922
10923 char *
10924 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10925 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10926       char *p, *variant = VariantName(v);
10927       static char b[MSG_SIZ];
10928       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10929            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10930                                                holdingsSize, variant); // cook up sized variant name
10931            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10932            if(StrStr(list, b) == NULL) {
10933                // specific sized variant not known, check if general sizing allowed
10934                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10935                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10936                             boardWidth, boardHeight, holdingsSize, engine);
10937                    return NULL;
10938                }
10939                /* [HGM] here we really should compare with the maximum supported board size */
10940            }
10941       } else snprintf(b, MSG_SIZ,"%s", variant);
10942       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10943       p = StrStr(list, b);
10944       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10945       if(p == NULL) {
10946           // occurs not at all in list, or only as sub-string
10947           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10948           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10949               int l = strlen(variantError);
10950               char *q;
10951               while(p != list && p[-1] != ',') p--;
10952               q = strchr(p, ',');
10953               if(q) *q = NULLCHAR;
10954               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10955               if(q) *q= ',';
10956           }
10957           return NULL;
10958       }
10959       return b;
10960 }
10961
10962 void
10963 InitChessProgram (ChessProgramState *cps, int setup)
10964 /* setup needed to setup FRC opening position */
10965 {
10966     char buf[MSG_SIZ], *b;
10967     if (appData.noChessProgram) return;
10968     hintRequested = FALSE;
10969     bookRequested = FALSE;
10970
10971     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10972     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10973     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10974     if(cps->memSize) { /* [HGM] memory */
10975       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10976         SendToProgram(buf, cps);
10977     }
10978     SendEgtPath(cps); /* [HGM] EGT */
10979     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10980       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10981         SendToProgram(buf, cps);
10982     }
10983
10984     setboardSpoiledMachineBlack = FALSE;
10985     SendToProgram(cps->initString, cps);
10986     if (gameInfo.variant != VariantNormal &&
10987         gameInfo.variant != VariantLoadable
10988         /* [HGM] also send variant if board size non-standard */
10989         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10990
10991       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10992                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10993
10994       if (b == NULL) {
10995         VariantClass v;
10996         char c, *q = cps->variants, *p = strchr(q, ',');
10997         if(p) *p = NULLCHAR;
10998         v = StringToVariant(q);
10999         DisplayError(variantError, 0);
11000         if(v != VariantUnknown && cps == &first) {
11001             int w, h, s;
11002             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
11003                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
11004             ASSIGN(appData.variant, q);
11005             Reset(TRUE, FALSE);
11006         }
11007         if(p) *p = ',';
11008         return;
11009       }
11010
11011       snprintf(buf, MSG_SIZ, "variant %s\n", b);
11012       SendToProgram(buf, cps);
11013     }
11014     currentlyInitializedVariant = gameInfo.variant;
11015
11016     /* [HGM] send opening position in FRC to first engine */
11017     if(setup) {
11018           SendToProgram("force\n", cps);
11019           SendBoard(cps, 0);
11020           /* engine is now in force mode! Set flag to wake it up after first move. */
11021           setboardSpoiledMachineBlack = 1;
11022     }
11023
11024     if (cps->sendICS) {
11025       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11026       SendToProgram(buf, cps);
11027     }
11028     cps->maybeThinking = FALSE;
11029     cps->offeredDraw = 0;
11030     if (!appData.icsActive) {
11031         SendTimeControl(cps, movesPerSession, timeControl,
11032                         timeIncrement, appData.searchDepth,
11033                         searchTime);
11034     }
11035     if (appData.showThinking
11036         // [HGM] thinking: four options require thinking output to be sent
11037         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11038                                 ) {
11039         SendToProgram("post\n", cps);
11040     }
11041     SendToProgram("hard\n", cps);
11042     if (!appData.ponderNextMove) {
11043         /* Warning: "easy" is a toggle in GNU Chess, so don't send
11044            it without being sure what state we are in first.  "hard"
11045            is not a toggle, so that one is OK.
11046          */
11047         SendToProgram("easy\n", cps);
11048     }
11049     if (cps->usePing) {
11050       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11051       SendToProgram(buf, cps);
11052     }
11053     cps->initDone = TRUE;
11054     ClearEngineOutputPane(cps == &second);
11055 }
11056
11057
11058 char *
11059 ResendOptions (ChessProgramState *cps, int toEngine)
11060 { // send the stored value of the options
11061   int i;
11062   static char buf2[MSG_SIZ*10];
11063   char buf[MSG_SIZ], *p = buf2;
11064   Option *opt = cps->option;
11065   *p = NULLCHAR;
11066   for(i=0; i<cps->nrOptions; i++, opt++) {
11067       *buf = NULLCHAR;
11068       switch(opt->type) {
11069         case Spin:
11070         case Slider:
11071         case CheckBox:
11072             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11073             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11074           break;
11075         case ComboBox:
11076             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11077             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11078           break;
11079         default:
11080             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11081             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11082           break;
11083         case Button:
11084         case SaveButton:
11085           continue;
11086       }
11087       if(*buf) {
11088         if(toEngine) {
11089           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11090           SendToProgram(buf2, cps);
11091         } else {
11092           if(p != buf2) *p++ = ',';
11093           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11094           while(*p) p++;
11095         }
11096       }
11097   }
11098   return buf2;
11099 }
11100
11101 void
11102 StartChessProgram (ChessProgramState *cps)
11103 {
11104     char buf[MSG_SIZ];
11105     int err;
11106
11107     if (appData.noChessProgram) return;
11108     cps->initDone = FALSE;
11109
11110     if (strcmp(cps->host, "localhost") == 0) {
11111         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11112     } else if (*appData.remoteShell == NULLCHAR) {
11113         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11114     } else {
11115         if (*appData.remoteUser == NULLCHAR) {
11116           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11117                     cps->program);
11118         } else {
11119           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11120                     cps->host, appData.remoteUser, cps->program);
11121         }
11122         err = StartChildProcess(buf, "", &cps->pr);
11123     }
11124
11125     if (err != 0) {
11126       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11127         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11128         if(cps != &first) return;
11129         appData.noChessProgram = TRUE;
11130         ThawUI();
11131         SetNCPMode();
11132 //      DisplayFatalError(buf, err, 1);
11133 //      cps->pr = NoProc;
11134 //      cps->isr = NULL;
11135         return;
11136     }
11137
11138     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11139     if (cps->protocolVersion > 1) {
11140       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11141       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11142         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11143         cps->comboCnt = 0;  //                and values of combo boxes
11144       }
11145       SendToProgram(buf, cps);
11146       if(cps->reload) ResendOptions(cps, TRUE);
11147     } else {
11148       SendToProgram("xboard\n", cps);
11149     }
11150 }
11151
11152 void
11153 TwoMachinesEventIfReady P((void))
11154 {
11155   static int curMess = 0;
11156   if (first.lastPing != first.lastPong) {
11157     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11158     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11159     return;
11160   }
11161   if (second.lastPing != second.lastPong) {
11162     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11163     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11164     return;
11165   }
11166   DisplayMessage("", ""); curMess = 0;
11167   TwoMachinesEvent();
11168 }
11169
11170 char *
11171 MakeName (char *template)
11172 {
11173     time_t clock;
11174     struct tm *tm;
11175     static char buf[MSG_SIZ];
11176     char *p = buf;
11177     int i;
11178
11179     clock = time((time_t *)NULL);
11180     tm = localtime(&clock);
11181
11182     while(*p++ = *template++) if(p[-1] == '%') {
11183         switch(*template++) {
11184           case 0:   *p = 0; return buf;
11185           case 'Y': i = tm->tm_year+1900; break;
11186           case 'y': i = tm->tm_year-100; break;
11187           case 'M': i = tm->tm_mon+1; break;
11188           case 'd': i = tm->tm_mday; break;
11189           case 'h': i = tm->tm_hour; break;
11190           case 'm': i = tm->tm_min; break;
11191           case 's': i = tm->tm_sec; break;
11192           default:  i = 0;
11193         }
11194         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11195     }
11196     return buf;
11197 }
11198
11199 int
11200 CountPlayers (char *p)
11201 {
11202     int n = 0;
11203     while(p = strchr(p, '\n')) p++, n++; // count participants
11204     return n;
11205 }
11206
11207 FILE *
11208 WriteTourneyFile (char *results, FILE *f)
11209 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11210     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11211     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11212         // create a file with tournament description
11213         fprintf(f, "-participants {%s}\n", appData.participants);
11214         fprintf(f, "-seedBase %d\n", appData.seedBase);
11215         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11216         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11217         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11218         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11219         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11220         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11221         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11222         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11223         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11224         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11225         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11226         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11227         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11228         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11229         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11230         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11231         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11232         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11233         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11234         fprintf(f, "-smpCores %d\n", appData.smpCores);
11235         if(searchTime > 0)
11236                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11237         else {
11238                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11239                 fprintf(f, "-tc %s\n", appData.timeControl);
11240                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11241         }
11242         fprintf(f, "-results \"%s\"\n", results);
11243     }
11244     return f;
11245 }
11246
11247 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11248
11249 void
11250 Substitute (char *participants, int expunge)
11251 {
11252     int i, changed, changes=0, nPlayers=0;
11253     char *p, *q, *r, buf[MSG_SIZ];
11254     if(participants == NULL) return;
11255     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11256     r = p = participants; q = appData.participants;
11257     while(*p && *p == *q) {
11258         if(*p == '\n') r = p+1, nPlayers++;
11259         p++; q++;
11260     }
11261     if(*p) { // difference
11262         while(*p && *p++ != '\n')
11263                                  ;
11264         while(*q && *q++ != '\n')
11265                                  ;
11266       changed = nPlayers;
11267         changes = 1 + (strcmp(p, q) != 0);
11268     }
11269     if(changes == 1) { // a single engine mnemonic was changed
11270         q = r; while(*q) nPlayers += (*q++ == '\n');
11271         p = buf; while(*r && (*p = *r++) != '\n') p++;
11272         *p = NULLCHAR;
11273         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11274         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11275         if(mnemonic[i]) { // The substitute is valid
11276             FILE *f;
11277             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11278                 flock(fileno(f), LOCK_EX);
11279                 ParseArgsFromFile(f);
11280                 fseek(f, 0, SEEK_SET);
11281                 FREE(appData.participants); appData.participants = participants;
11282                 if(expunge) { // erase results of replaced engine
11283                     int len = strlen(appData.results), w, b, dummy;
11284                     for(i=0; i<len; i++) {
11285                         Pairing(i, nPlayers, &w, &b, &dummy);
11286                         if((w == changed || b == changed) && appData.results[i] == '*') {
11287                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11288                             fclose(f);
11289                             return;
11290                         }
11291                     }
11292                     for(i=0; i<len; i++) {
11293                         Pairing(i, nPlayers, &w, &b, &dummy);
11294                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11295                     }
11296                 }
11297                 WriteTourneyFile(appData.results, f);
11298                 fclose(f); // release lock
11299                 return;
11300             }
11301         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11302     }
11303     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11304     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11305     free(participants);
11306     return;
11307 }
11308
11309 int
11310 CheckPlayers (char *participants)
11311 {
11312         int i;
11313         char buf[MSG_SIZ], *p;
11314         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11315         while(p = strchr(participants, '\n')) {
11316             *p = NULLCHAR;
11317             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11318             if(!mnemonic[i]) {
11319                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11320                 *p = '\n';
11321                 DisplayError(buf, 0);
11322                 return 1;
11323             }
11324             *p = '\n';
11325             participants = p + 1;
11326         }
11327         return 0;
11328 }
11329
11330 int
11331 CreateTourney (char *name)
11332 {
11333         FILE *f;
11334         if(matchMode && strcmp(name, appData.tourneyFile)) {
11335              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11336         }
11337         if(name[0] == NULLCHAR) {
11338             if(appData.participants[0])
11339                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11340             return 0;
11341         }
11342         f = fopen(name, "r");
11343         if(f) { // file exists
11344             ASSIGN(appData.tourneyFile, name);
11345             ParseArgsFromFile(f); // parse it
11346         } else {
11347             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11348             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11349                 DisplayError(_("Not enough participants"), 0);
11350                 return 0;
11351             }
11352             if(CheckPlayers(appData.participants)) return 0;
11353             ASSIGN(appData.tourneyFile, name);
11354             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11355             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11356         }
11357         fclose(f);
11358         appData.noChessProgram = FALSE;
11359         appData.clockMode = TRUE;
11360         SetGNUMode();
11361         return 1;
11362 }
11363
11364 int
11365 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11366 {
11367     char buf[2*MSG_SIZ], *p, *q;
11368     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11369     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11370     skip = !all && group[0]; // if group requested, we start in skip mode
11371     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11372         p = names; q = buf; header = 0;
11373         while(*p && *p != '\n') *q++ = *p++;
11374         *q = 0;
11375         if(*p == '\n') p++;
11376         if(buf[0] == '#') {
11377             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11378             depth++; // we must be entering a new group
11379             if(all) continue; // suppress printing group headers when complete list requested
11380             header = 1;
11381             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11382         }
11383         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11384         if(engineList[i]) free(engineList[i]);
11385         engineList[i] = strdup(buf);
11386         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11387         if(engineMnemonic[i]) free(engineMnemonic[i]);
11388         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11389             strcat(buf, " (");
11390             sscanf(q + 8, "%s", buf + strlen(buf));
11391             strcat(buf, ")");
11392         }
11393         engineMnemonic[i] = strdup(buf);
11394         i++;
11395     }
11396     engineList[i] = engineMnemonic[i] = NULL;
11397     return i;
11398 }
11399
11400 void
11401 SaveEngineSettings (int n)
11402 {
11403     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11404     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11405     if(*engineListFile) ParseSettingsFile(engineListFile, &engineListFile); // update engine list
11406     p = strstr(firstChessProgramNames, currentEngine[n]);
11407     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11408     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11409     len = strlen(currentEngine[n]);
11410     q = p + len; *p = 0; // cut list into head and tail piece
11411     s = strstr(currentEngine[n], "firstOptions");
11412     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11413         char *r = s + 14;
11414         while(*r && *r != s[13]) r++;
11415         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11416         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11417     } else if(*optionSettings) {
11418         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11419     }
11420     ASSIGN(currentEngine[n], buf); // updated engine line
11421     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11422     s = malloc(len);
11423     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11424     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11425     if(*engineListFile) SaveEngineList();
11426 }
11427
11428 // following implemented as macro to avoid type limitations
11429 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11430
11431 void
11432 SwapEngines (int n)
11433 {   // swap settings for first engine and other engine (so far only some selected options)
11434     int h;
11435     char *p;
11436     if(n == 0) return;
11437     SWAP(directory, p)
11438     SWAP(chessProgram, p)
11439     SWAP(isUCI, h)
11440     SWAP(hasOwnBookUCI, h)
11441     SWAP(protocolVersion, h)
11442     SWAP(reuse, h)
11443     SWAP(scoreIsAbsolute, h)
11444     SWAP(timeOdds, h)
11445     SWAP(logo, p)
11446     SWAP(pgnName, p)
11447     SWAP(pvSAN, h)
11448     SWAP(engOptions, p)
11449     SWAP(engInitString, p)
11450     SWAP(computerString, p)
11451     SWAP(features, p)
11452     SWAP(fenOverride, p)
11453     SWAP(NPS, h)
11454     SWAP(accumulateTC, h)
11455     SWAP(drawDepth, h)
11456     SWAP(host, p)
11457     SWAP(pseudo, h)
11458 }
11459
11460 int
11461 GetEngineLine (char *s, int n)
11462 {
11463     int i;
11464     char buf[MSG_SIZ];
11465     extern char *icsNames;
11466     if(!s || !*s) return 0;
11467     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11468     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11469     if(!mnemonic[i]) return 0;
11470     if(n == 11) return 1; // just testing if there was a match
11471     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11472     if(n == 1) SwapEngines(n);
11473     ParseArgsFromString(buf);
11474     if(n == 1) SwapEngines(n);
11475     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11476     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11477         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11478         ParseArgsFromString(buf);
11479     }
11480     return 1;
11481 }
11482
11483 int
11484 SetPlayer (int player, char *p)
11485 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11486     int i;
11487     char buf[MSG_SIZ], *engineName;
11488     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11489     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11490     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11491     if(mnemonic[i]) {
11492         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11493         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11494         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11495         ParseArgsFromString(buf);
11496     } else { // no engine with this nickname is installed!
11497         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11498         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11499         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11500         ModeHighlight();
11501         DisplayError(buf, 0);
11502         return 0;
11503     }
11504     free(engineName);
11505     return i;
11506 }
11507
11508 char *recentEngines;
11509
11510 void
11511 RecentEngineEvent (int nr)
11512 {
11513     int n;
11514 //    SwapEngines(1); // bump first to second
11515 //    ReplaceEngine(&second, 1); // and load it there
11516     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11517     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11518     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11519         ReplaceEngine(&first, 0);
11520         FloatToFront(&appData.recentEngineList, command[n]);
11521         ASSIGN(currentEngine[0], command[n]);
11522     }
11523 }
11524
11525 int
11526 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11527 {   // determine players from game number
11528     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11529
11530     if(appData.tourneyType == 0) {
11531         roundsPerCycle = (nPlayers - 1) | 1;
11532         pairingsPerRound = nPlayers / 2;
11533     } else if(appData.tourneyType > 0) {
11534         roundsPerCycle = nPlayers - appData.tourneyType;
11535         pairingsPerRound = appData.tourneyType;
11536     }
11537     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11538     gamesPerCycle = gamesPerRound * roundsPerCycle;
11539     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11540     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11541     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11542     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11543     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11544     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11545
11546     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11547     if(appData.roundSync) *syncInterval = gamesPerRound;
11548
11549     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11550
11551     if(appData.tourneyType == 0) {
11552         if(curPairing == (nPlayers-1)/2 ) {
11553             *whitePlayer = curRound;
11554             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11555         } else {
11556             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11557             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11558             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11559             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11560         }
11561     } else if(appData.tourneyType > 1) {
11562         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11563         *whitePlayer = curRound + appData.tourneyType;
11564     } else if(appData.tourneyType > 0) {
11565         *whitePlayer = curPairing;
11566         *blackPlayer = curRound + appData.tourneyType;
11567     }
11568
11569     // take care of white/black alternation per round.
11570     // For cycles and games this is already taken care of by default, derived from matchGame!
11571     return curRound & 1;
11572 }
11573
11574 int
11575 NextTourneyGame (int nr, int *swapColors)
11576 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11577     char *p, *q;
11578     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11579     FILE *tf;
11580     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11581     tf = fopen(appData.tourneyFile, "r");
11582     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11583     ParseArgsFromFile(tf); fclose(tf);
11584     InitTimeControls(); // TC might be altered from tourney file
11585
11586     nPlayers = CountPlayers(appData.participants); // count participants
11587     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11588     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11589
11590     if(syncInterval) {
11591         p = q = appData.results;
11592         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11593         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11594             DisplayMessage(_("Waiting for other game(s)"),"");
11595             waitingForGame = TRUE;
11596             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11597             return 0;
11598         }
11599         waitingForGame = FALSE;
11600     }
11601
11602     if(appData.tourneyType < 0) {
11603         if(nr>=0 && !pairingReceived) {
11604             char buf[1<<16];
11605             if(pairing.pr == NoProc) {
11606                 if(!appData.pairingEngine[0]) {
11607                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11608                     return 0;
11609                 }
11610                 StartChessProgram(&pairing); // starts the pairing engine
11611             }
11612             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11613             SendToProgram(buf, &pairing);
11614             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11615             SendToProgram(buf, &pairing);
11616             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11617         }
11618         pairingReceived = 0;                              // ... so we continue here
11619         *swapColors = 0;
11620         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11621         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11622         matchGame = 1; roundNr = nr / syncInterval + 1;
11623     }
11624
11625     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11626
11627     // redefine engines, engine dir, etc.
11628     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11629     if(first.pr == NoProc) {
11630       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11631       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11632     }
11633     if(second.pr == NoProc) {
11634       SwapEngines(1);
11635       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11636       SwapEngines(1);         // and make that valid for second engine by swapping
11637       InitEngine(&second, 1);
11638     }
11639     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11640     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11641     return OK;
11642 }
11643
11644 void
11645 NextMatchGame ()
11646 {   // performs game initialization that does not invoke engines, and then tries to start the game
11647     int res, firstWhite, swapColors = 0;
11648     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11649     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
11650         char buf[MSG_SIZ];
11651         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11652         if(strcmp(buf, currentDebugFile)) { // name has changed
11653             FILE *f = fopen(buf, "w");
11654             if(f) { // if opening the new file failed, just keep using the old one
11655                 ASSIGN(currentDebugFile, buf);
11656                 fclose(debugFP);
11657                 debugFP = f;
11658             }
11659             if(appData.serverFileName) {
11660                 if(serverFP) fclose(serverFP);
11661                 serverFP = fopen(appData.serverFileName, "w");
11662                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11663                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11664             }
11665         }
11666     }
11667     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11668     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11669     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11670     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11671     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11672     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11673     Reset(FALSE, first.pr != NoProc);
11674     res = LoadGameOrPosition(matchGame); // setup game
11675     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11676     if(!res) return; // abort when bad game/pos file
11677     if(appData.epd) {// in EPD mode we make sure first engine is to move
11678         firstWhite = !(forwardMostMove & 1);
11679         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11680         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11681     }
11682     TwoMachinesEvent();
11683 }
11684
11685 void
11686 UserAdjudicationEvent (int result)
11687 {
11688     ChessMove gameResult = GameIsDrawn;
11689
11690     if( result > 0 ) {
11691         gameResult = WhiteWins;
11692     }
11693     else if( result < 0 ) {
11694         gameResult = BlackWins;
11695     }
11696
11697     if( gameMode == TwoMachinesPlay ) {
11698         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11699     }
11700 }
11701
11702
11703 // [HGM] save: calculate checksum of game to make games easily identifiable
11704 int
11705 StringCheckSum (char *s)
11706 {
11707         int i = 0;
11708         if(s==NULL) return 0;
11709         while(*s) i = i*259 + *s++;
11710         return i;
11711 }
11712
11713 int
11714 GameCheckSum ()
11715 {
11716         int i, sum=0;
11717         for(i=backwardMostMove; i<forwardMostMove; i++) {
11718                 sum += pvInfoList[i].depth;
11719                 sum += StringCheckSum(parseList[i]);
11720                 sum += StringCheckSum(commentList[i]);
11721                 sum *= 261;
11722         }
11723         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11724         return sum + StringCheckSum(commentList[i]);
11725 } // end of save patch
11726
11727 void
11728 GameEnds (ChessMove result, char *resultDetails, int whosays)
11729 {
11730     GameMode nextGameMode;
11731     int isIcsGame;
11732     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11733
11734     if(endingGame) return; /* [HGM] crash: forbid recursion */
11735     endingGame = 1;
11736     if(twoBoards) { // [HGM] dual: switch back to one board
11737         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11738         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11739     }
11740     if (appData.debugMode) {
11741       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11742               result, resultDetails ? resultDetails : "(null)", whosays);
11743     }
11744
11745     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11746
11747     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11748
11749     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11750         /* If we are playing on ICS, the server decides when the
11751            game is over, but the engine can offer to draw, claim
11752            a draw, or resign.
11753          */
11754 #if ZIPPY
11755         if (appData.zippyPlay && first.initDone) {
11756             if (result == GameIsDrawn) {
11757                 /* In case draw still needs to be claimed */
11758                 SendToICS(ics_prefix);
11759                 SendToICS("draw\n");
11760             } else if (StrCaseStr(resultDetails, "resign")) {
11761                 SendToICS(ics_prefix);
11762                 SendToICS("resign\n");
11763             }
11764         }
11765 #endif
11766         endingGame = 0; /* [HGM] crash */
11767         return;
11768     }
11769
11770     /* If we're loading the game from a file, stop */
11771     if (whosays == GE_FILE) {
11772       (void) StopLoadGameTimer();
11773       gameFileFP = NULL;
11774     }
11775
11776     /* Cancel draw offers */
11777     first.offeredDraw = second.offeredDraw = 0;
11778
11779     /* If this is an ICS game, only ICS can really say it's done;
11780        if not, anyone can. */
11781     isIcsGame = (gameMode == IcsPlayingWhite ||
11782                  gameMode == IcsPlayingBlack ||
11783                  gameMode == IcsObserving    ||
11784                  gameMode == IcsExamining);
11785
11786     if (!isIcsGame || whosays == GE_ICS) {
11787         /* OK -- not an ICS game, or ICS said it was done */
11788         StopClocks();
11789         if (!isIcsGame && !appData.noChessProgram)
11790           SetUserThinkingEnables();
11791
11792         /* [HGM] if a machine claims the game end we verify this claim */
11793         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11794             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11795                 char claimer;
11796                 ChessMove trueResult = (ChessMove) -1;
11797
11798                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11799                                             first.twoMachinesColor[0] :
11800                                             second.twoMachinesColor[0] ;
11801
11802                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11803                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11804                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11805                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11806                 } else
11807                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11808                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11809                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11810                 } else
11811                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11812                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11813                 }
11814
11815                 // now verify win claims, but not in drop games, as we don't understand those yet
11816                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11817                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11818                     (result == WhiteWins && claimer == 'w' ||
11819                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11820                       if (appData.debugMode) {
11821                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11822                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11823                       }
11824                       if(result != trueResult) {
11825                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11826                               result = claimer == 'w' ? BlackWins : WhiteWins;
11827                               resultDetails = buf;
11828                       }
11829                 } else
11830                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11831                     && (forwardMostMove <= backwardMostMove ||
11832                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11833                         (claimer=='b')==(forwardMostMove&1))
11834                                                                                   ) {
11835                       /* [HGM] verify: draws that were not flagged are false claims */
11836                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11837                       result = claimer == 'w' ? BlackWins : WhiteWins;
11838                       resultDetails = buf;
11839                 }
11840                 /* (Claiming a loss is accepted no questions asked!) */
11841             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11842                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11843                 result = GameUnfinished;
11844                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11845             }
11846             /* [HGM] bare: don't allow bare King to win */
11847             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11848                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11849                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11850                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11851                && result != GameIsDrawn)
11852             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11853                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11854                         int p = (int)boards[forwardMostMove][i][j] - color;
11855                         if(p >= 0 && p <= (int)WhiteKing) k++;
11856                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11857                 }
11858                 if (appData.debugMode) {
11859                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11860                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11861                 }
11862                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11863                         result = GameIsDrawn;
11864                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11865                         resultDetails = buf;
11866                 }
11867             }
11868         }
11869
11870
11871         if(serverMoves != NULL && !loadFlag) { char c = '=';
11872             if(result==WhiteWins) c = '+';
11873             if(result==BlackWins) c = '-';
11874             if(resultDetails != NULL)
11875                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11876         }
11877         if (resultDetails != NULL) {
11878             gameInfo.result = result;
11879             gameInfo.resultDetails = StrSave(resultDetails);
11880
11881             /* display last move only if game was not loaded from file */
11882             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11883                 DisplayMove(currentMove - 1);
11884
11885             if (forwardMostMove != 0) {
11886                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11887                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11888                                                                 ) {
11889                     if (*appData.saveGameFile != NULLCHAR) {
11890                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11891                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11892                         else
11893                         SaveGameToFile(appData.saveGameFile, TRUE);
11894                     } else if (appData.autoSaveGames) {
11895                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11896                     }
11897                     if (*appData.savePositionFile != NULLCHAR) {
11898                         SavePositionToFile(appData.savePositionFile);
11899                     }
11900                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11901                 }
11902             }
11903
11904             /* Tell program how game ended in case it is learning */
11905             /* [HGM] Moved this to after saving the PGN, just in case */
11906             /* engine died and we got here through time loss. In that */
11907             /* case we will get a fatal error writing the pipe, which */
11908             /* would otherwise lose us the PGN.                       */
11909             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11910             /* output during GameEnds should never be fatal anymore   */
11911             if (gameMode == MachinePlaysWhite ||
11912                 gameMode == MachinePlaysBlack ||
11913                 gameMode == TwoMachinesPlay ||
11914                 gameMode == IcsPlayingWhite ||
11915                 gameMode == IcsPlayingBlack ||
11916                 gameMode == BeginningOfGame) {
11917                 char buf[MSG_SIZ];
11918                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11919                         resultDetails);
11920                 if (first.pr != NoProc) {
11921                     SendToProgram(buf, &first);
11922                 }
11923                 if (second.pr != NoProc &&
11924                     gameMode == TwoMachinesPlay) {
11925                     SendToProgram(buf, &second);
11926                 }
11927             }
11928         }
11929
11930         if (appData.icsActive) {
11931             if (appData.quietPlay &&
11932                 (gameMode == IcsPlayingWhite ||
11933                  gameMode == IcsPlayingBlack)) {
11934                 SendToICS(ics_prefix);
11935                 SendToICS("set shout 1\n");
11936             }
11937             nextGameMode = IcsIdle;
11938             ics_user_moved = FALSE;
11939             /* clean up premove.  It's ugly when the game has ended and the
11940              * premove highlights are still on the board.
11941              */
11942             if (gotPremove) {
11943               gotPremove = FALSE;
11944               ClearPremoveHighlights();
11945               DrawPosition(FALSE, boards[currentMove]);
11946             }
11947             if (whosays == GE_ICS) {
11948                 switch (result) {
11949                 case WhiteWins:
11950                     if (gameMode == IcsPlayingWhite)
11951                         PlayIcsWinSound();
11952                     else if(gameMode == IcsPlayingBlack)
11953                         PlayIcsLossSound();
11954                     break;
11955                 case BlackWins:
11956                     if (gameMode == IcsPlayingBlack)
11957                         PlayIcsWinSound();
11958                     else if(gameMode == IcsPlayingWhite)
11959                         PlayIcsLossSound();
11960                     break;
11961                 case GameIsDrawn:
11962                     PlayIcsDrawSound();
11963                     break;
11964                 default:
11965                     PlayIcsUnfinishedSound();
11966                 }
11967             }
11968             if(appData.quitNext) { ExitEvent(0); return; }
11969         } else if (gameMode == EditGame ||
11970                    gameMode == PlayFromGameFile ||
11971                    gameMode == AnalyzeMode ||
11972                    gameMode == AnalyzeFile) {
11973             nextGameMode = gameMode;
11974         } else {
11975             nextGameMode = EndOfGame;
11976         }
11977         pausing = FALSE;
11978         ModeHighlight();
11979     } else {
11980         nextGameMode = gameMode;
11981     }
11982
11983     if (appData.noChessProgram) {
11984         gameMode = nextGameMode;
11985         ModeHighlight();
11986         endingGame = 0; /* [HGM] crash */
11987         return;
11988     }
11989
11990     if (first.reuse) {
11991         /* Put first chess program into idle state */
11992         if (first.pr != NoProc &&
11993             (gameMode == MachinePlaysWhite ||
11994              gameMode == MachinePlaysBlack ||
11995              gameMode == TwoMachinesPlay ||
11996              gameMode == IcsPlayingWhite ||
11997              gameMode == IcsPlayingBlack ||
11998              gameMode == BeginningOfGame)) {
11999             SendToProgram("force\n", &first);
12000             if (first.usePing) {
12001               char buf[MSG_SIZ];
12002               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
12003               SendToProgram(buf, &first);
12004             }
12005         }
12006     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12007         /* Kill off first chess program */
12008         if (first.isr != NULL)
12009           RemoveInputSource(first.isr);
12010         first.isr = NULL;
12011
12012         if (first.pr != NoProc) {
12013             ExitAnalyzeMode();
12014             DoSleep( appData.delayBeforeQuit );
12015             SendToProgram("quit\n", &first);
12016             DestroyChildProcess(first.pr, 4 + first.useSigterm);
12017             first.reload = TRUE;
12018         }
12019         first.pr = NoProc;
12020     }
12021     if (second.reuse) {
12022         /* Put second chess program into idle state */
12023         if (second.pr != NoProc &&
12024             gameMode == TwoMachinesPlay) {
12025             SendToProgram("force\n", &second);
12026             if (second.usePing) {
12027               char buf[MSG_SIZ];
12028               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12029               SendToProgram(buf, &second);
12030             }
12031         }
12032     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12033         /* Kill off second chess program */
12034         if (second.isr != NULL)
12035           RemoveInputSource(second.isr);
12036         second.isr = NULL;
12037
12038         if (second.pr != NoProc) {
12039             DoSleep( appData.delayBeforeQuit );
12040             SendToProgram("quit\n", &second);
12041             DestroyChildProcess(second.pr, 4 + second.useSigterm);
12042             second.reload = TRUE;
12043         }
12044         second.pr = NoProc;
12045     }
12046
12047     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12048         char resChar = '=';
12049         switch (result) {
12050         case WhiteWins:
12051           resChar = '+';
12052           if (first.twoMachinesColor[0] == 'w') {
12053             first.matchWins++;
12054           } else {
12055             second.matchWins++;
12056           }
12057           break;
12058         case BlackWins:
12059           resChar = '-';
12060           if (first.twoMachinesColor[0] == 'b') {
12061             first.matchWins++;
12062           } else {
12063             second.matchWins++;
12064           }
12065           break;
12066         case GameUnfinished:
12067           resChar = ' ';
12068         default:
12069           break;
12070         }
12071
12072         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12073         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12074             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12075             ReserveGame(nextGame, resChar); // sets nextGame
12076             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12077             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12078         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12079
12080         if (nextGame <= appData.matchGames && !abortMatch) {
12081             gameMode = nextGameMode;
12082             matchGame = nextGame; // this will be overruled in tourney mode!
12083             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12084             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12085             endingGame = 0; /* [HGM] crash */
12086             return;
12087         } else {
12088             gameMode = nextGameMode;
12089             if(appData.epd) {
12090                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12091                 OutputKibitz(2, buf);
12092                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12093                 OutputKibitz(2, buf);
12094                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12095                 if(second.matchWins) OutputKibitz(2, buf);
12096                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12097                 OutputKibitz(2, buf);
12098             }
12099             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12100                      first.tidy, second.tidy,
12101                      first.matchWins, second.matchWins,
12102                      appData.matchGames - (first.matchWins + second.matchWins));
12103             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12104             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12105             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12106             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12107                 first.twoMachinesColor = "black\n";
12108                 second.twoMachinesColor = "white\n";
12109             } else {
12110                 first.twoMachinesColor = "white\n";
12111                 second.twoMachinesColor = "black\n";
12112             }
12113         }
12114     }
12115     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12116         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12117       ExitAnalyzeMode();
12118     gameMode = nextGameMode;
12119     ModeHighlight();
12120     endingGame = 0;  /* [HGM] crash */
12121     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12122         if(matchMode == TRUE) { // match through command line: exit with or without popup
12123             if(ranking) {
12124                 ToNrEvent(forwardMostMove);
12125                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12126                 else ExitEvent(0);
12127             } else DisplayFatalError(buf, 0, 0);
12128         } else { // match through menu; just stop, with or without popup
12129             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12130             ModeHighlight();
12131             if(ranking){
12132                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12133             } else DisplayNote(buf);
12134       }
12135       if(ranking) free(ranking);
12136     }
12137 }
12138
12139 /* Assumes program was just initialized (initString sent).
12140    Leaves program in force mode. */
12141 void
12142 FeedMovesToProgram (ChessProgramState *cps, int upto)
12143 {
12144     int i;
12145
12146     if (appData.debugMode)
12147       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12148               startedFromSetupPosition ? "position and " : "",
12149               backwardMostMove, upto, cps->which);
12150     if(currentlyInitializedVariant != gameInfo.variant) {
12151       char buf[MSG_SIZ];
12152         // [HGM] variantswitch: make engine aware of new variant
12153         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12154                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12155                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12156         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12157         SendToProgram(buf, cps);
12158         currentlyInitializedVariant = gameInfo.variant;
12159     }
12160     SendToProgram("force\n", cps);
12161     if (startedFromSetupPosition) {
12162         SendBoard(cps, backwardMostMove);
12163     if (appData.debugMode) {
12164         fprintf(debugFP, "feedMoves\n");
12165     }
12166     }
12167     for (i = backwardMostMove; i < upto; i++) {
12168         SendMoveToProgram(i, cps);
12169     }
12170 }
12171
12172
12173 int
12174 ResurrectChessProgram ()
12175 {
12176      /* The chess program may have exited.
12177         If so, restart it and feed it all the moves made so far. */
12178     static int doInit = 0;
12179
12180     if (appData.noChessProgram) return 1;
12181
12182     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12183         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12184         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12185         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12186     } else {
12187         if (first.pr != NoProc) return 1;
12188         StartChessProgram(&first);
12189     }
12190     InitChessProgram(&first, FALSE);
12191     FeedMovesToProgram(&first, currentMove);
12192
12193     if (!first.sendTime) {
12194         /* can't tell gnuchess what its clock should read,
12195            so we bow to its notion. */
12196         ResetClocks();
12197         timeRemaining[0][currentMove] = whiteTimeRemaining;
12198         timeRemaining[1][currentMove] = blackTimeRemaining;
12199     }
12200
12201     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12202                 appData.icsEngineAnalyze) && first.analysisSupport) {
12203       SendToProgram("analyze\n", &first);
12204       first.analyzing = TRUE;
12205     }
12206     return 1;
12207 }
12208
12209 /*
12210  * Button procedures
12211  */
12212 void
12213 Reset (int redraw, int init)
12214 {
12215     int i;
12216
12217     if (appData.debugMode) {
12218         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12219                 redraw, init, gameMode);
12220     }
12221     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12222     deadRanks = 0; // assume entire board is used
12223     handSize = 0;
12224     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12225     CleanupTail(); // [HGM] vari: delete any stored variations
12226     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12227     pausing = pauseExamInvalid = FALSE;
12228     startedFromSetupPosition = blackPlaysFirst = FALSE;
12229     firstMove = TRUE;
12230     whiteFlag = blackFlag = FALSE;
12231     userOfferedDraw = FALSE;
12232     hintRequested = bookRequested = FALSE;
12233     first.maybeThinking = FALSE;
12234     second.maybeThinking = FALSE;
12235     first.bookSuspend = FALSE; // [HGM] book
12236     second.bookSuspend = FALSE;
12237     thinkOutput[0] = NULLCHAR;
12238     lastHint[0] = NULLCHAR;
12239     ClearGameInfo(&gameInfo);
12240     gameInfo.variant = StringToVariant(appData.variant);
12241     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12242         gameInfo.variant = VariantUnknown;
12243         strncpy(engineVariant, appData.variant, MSG_SIZ);
12244     }
12245     ics_user_moved = ics_clock_paused = FALSE;
12246     ics_getting_history = H_FALSE;
12247     ics_gamenum = -1;
12248     white_holding[0] = black_holding[0] = NULLCHAR;
12249     ClearProgramStats();
12250     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12251
12252     ResetFrontEnd();
12253     ClearHighlights();
12254     flipView = appData.flipView;
12255     ClearPremoveHighlights();
12256     gotPremove = FALSE;
12257     alarmSounded = FALSE;
12258     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12259
12260     GameEnds(EndOfFile, NULL, GE_PLAYER);
12261     if(appData.serverMovesName != NULL) {
12262         /* [HGM] prepare to make moves file for broadcasting */
12263         clock_t t = clock();
12264         if(serverMoves != NULL) fclose(serverMoves);
12265         serverMoves = fopen(appData.serverMovesName, "r");
12266         if(serverMoves != NULL) {
12267             fclose(serverMoves);
12268             /* delay 15 sec before overwriting, so all clients can see end */
12269             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12270         }
12271         serverMoves = fopen(appData.serverMovesName, "w");
12272     }
12273
12274     ExitAnalyzeMode();
12275     gameMode = BeginningOfGame;
12276     ModeHighlight();
12277     if(appData.icsActive) gameInfo.variant = VariantNormal;
12278     currentMove = forwardMostMove = backwardMostMove = 0;
12279     MarkTargetSquares(1);
12280     InitPosition(redraw);
12281     for (i = 0; i < MAX_MOVES; i++) {
12282         if (commentList[i] != NULL) {
12283             free(commentList[i]);
12284             commentList[i] = NULL;
12285         }
12286     }
12287     ResetClocks();
12288     timeRemaining[0][0] = whiteTimeRemaining;
12289     timeRemaining[1][0] = blackTimeRemaining;
12290
12291     if (first.pr == NoProc) {
12292         StartChessProgram(&first);
12293     }
12294     if (init) {
12295             InitChessProgram(&first, startedFromSetupPosition);
12296     }
12297     DisplayTitle("");
12298     DisplayMessage("", "");
12299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12300     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12301     ClearMap();        // [HGM] exclude: invalidate map
12302 }
12303
12304 void
12305 AutoPlayGameLoop ()
12306 {
12307     for (;;) {
12308         if (!AutoPlayOneMove())
12309           return;
12310         if (matchMode || appData.timeDelay == 0)
12311           continue;
12312         if (appData.timeDelay < 0)
12313           return;
12314         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12315         break;
12316     }
12317 }
12318
12319 void
12320 AnalyzeNextGame()
12321 {
12322     ReloadGame(1); // next game
12323 }
12324
12325 int
12326 AutoPlayOneMove ()
12327 {
12328     int fromX, fromY, toX, toY;
12329
12330     if (appData.debugMode) {
12331       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12332     }
12333
12334     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12335       return FALSE;
12336
12337     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12338       pvInfoList[currentMove].depth = programStats.depth;
12339       pvInfoList[currentMove].score = programStats.score;
12340       pvInfoList[currentMove].time  = 0;
12341       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12342       else { // append analysis of final position as comment
12343         char buf[MSG_SIZ];
12344         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12345         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12346       }
12347       programStats.depth = 0;
12348     }
12349
12350     if (currentMove >= forwardMostMove) {
12351       if(gameMode == AnalyzeFile) {
12352           if(appData.loadGameIndex == -1) {
12353             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12354           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12355           } else {
12356           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12357         }
12358       }
12359 //      gameMode = EndOfGame;
12360 //      ModeHighlight();
12361
12362       /* [AS] Clear current move marker at the end of a game */
12363       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12364
12365       return FALSE;
12366     }
12367
12368     toX = moveList[currentMove][2] - AAA;
12369     toY = moveList[currentMove][3] - ONE;
12370
12371     if (moveList[currentMove][1] == '@') {
12372         if (appData.highlightLastMove) {
12373             SetHighlights(-1, -1, toX, toY);
12374         }
12375     } else {
12376         fromX = moveList[currentMove][0] - AAA;
12377         fromY = moveList[currentMove][1] - ONE;
12378
12379         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12380
12381         if(moveList[currentMove][4] == ';') { // multi-leg
12382             killX = moveList[currentMove][5] - AAA;
12383             killY = moveList[currentMove][6] - ONE;
12384         }
12385         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12386         killX = killY = -1;
12387
12388         if (appData.highlightLastMove) {
12389             SetHighlights(fromX, fromY, toX, toY);
12390         }
12391     }
12392     DisplayMove(currentMove);
12393     SendMoveToProgram(currentMove++, &first);
12394     DisplayBothClocks();
12395     DrawPosition(FALSE, boards[currentMove]);
12396     // [HGM] PV info: always display, routine tests if empty
12397     DisplayComment(currentMove - 1, commentList[currentMove]);
12398     return TRUE;
12399 }
12400
12401
12402 int
12403 LoadGameOneMove (ChessMove readAhead)
12404 {
12405     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12406     char promoChar = NULLCHAR;
12407     ChessMove moveType;
12408     char move[MSG_SIZ];
12409     char *p, *q;
12410
12411     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12412         gameMode != AnalyzeMode && gameMode != Training) {
12413         gameFileFP = NULL;
12414         return FALSE;
12415     }
12416
12417     yyboardindex = forwardMostMove;
12418     if (readAhead != EndOfFile) {
12419       moveType = readAhead;
12420     } else {
12421       if (gameFileFP == NULL)
12422           return FALSE;
12423       moveType = (ChessMove) Myylex();
12424     }
12425
12426     done = FALSE;
12427     switch (moveType) {
12428       case Comment:
12429         if (appData.debugMode)
12430           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12431         p = yy_text;
12432
12433         /* append the comment but don't display it */
12434         AppendComment(currentMove, p, FALSE);
12435         return TRUE;
12436
12437       case WhiteCapturesEnPassant:
12438       case BlackCapturesEnPassant:
12439       case WhitePromotion:
12440       case BlackPromotion:
12441       case WhiteNonPromotion:
12442       case BlackNonPromotion:
12443       case NormalMove:
12444       case FirstLeg:
12445       case WhiteKingSideCastle:
12446       case WhiteQueenSideCastle:
12447       case BlackKingSideCastle:
12448       case BlackQueenSideCastle:
12449       case WhiteKingSideCastleWild:
12450       case WhiteQueenSideCastleWild:
12451       case BlackKingSideCastleWild:
12452       case BlackQueenSideCastleWild:
12453       /* PUSH Fabien */
12454       case WhiteHSideCastleFR:
12455       case WhiteASideCastleFR:
12456       case BlackHSideCastleFR:
12457       case BlackASideCastleFR:
12458       /* POP Fabien */
12459         if (appData.debugMode)
12460           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12461         fromX = currentMoveString[0] - AAA;
12462         fromY = currentMoveString[1] - ONE;
12463         toX = currentMoveString[2] - AAA;
12464         toY = currentMoveString[3] - ONE;
12465         promoChar = currentMoveString[4];
12466         if(promoChar == ';') promoChar = currentMoveString[7];
12467         break;
12468
12469       case WhiteDrop:
12470       case BlackDrop:
12471         if (appData.debugMode)
12472           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12473         fromX = moveType == WhiteDrop ?
12474           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12475         (int) CharToPiece(ToLower(currentMoveString[0]));
12476         fromY = DROP_RANK;
12477         toX = currentMoveString[2] - AAA;
12478         toY = currentMoveString[3] - ONE;
12479         break;
12480
12481       case WhiteWins:
12482       case BlackWins:
12483       case GameIsDrawn:
12484       case GameUnfinished:
12485         if (appData.debugMode)
12486           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12487         p = strchr(yy_text, '{');
12488         if (p == NULL) p = strchr(yy_text, '(');
12489         if (p == NULL) {
12490             p = yy_text;
12491             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12492         } else {
12493             q = strchr(p, *p == '{' ? '}' : ')');
12494             if (q != NULL) *q = NULLCHAR;
12495             p++;
12496         }
12497         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12498         GameEnds(moveType, p, GE_FILE);
12499         done = TRUE;
12500         if (cmailMsgLoaded) {
12501             ClearHighlights();
12502             flipView = WhiteOnMove(currentMove);
12503             if (moveType == GameUnfinished) flipView = !flipView;
12504             if (appData.debugMode)
12505               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12506         }
12507         break;
12508
12509       case EndOfFile:
12510         if (appData.debugMode)
12511           fprintf(debugFP, "Parser hit end of file\n");
12512         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12513           case MT_NONE:
12514           case MT_CHECK:
12515             break;
12516           case MT_CHECKMATE:
12517           case MT_STAINMATE:
12518             if (WhiteOnMove(currentMove)) {
12519                 GameEnds(BlackWins, "Black mates", GE_FILE);
12520             } else {
12521                 GameEnds(WhiteWins, "White mates", GE_FILE);
12522             }
12523             break;
12524           case MT_STALEMATE:
12525             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12526             break;
12527         }
12528         done = TRUE;
12529         break;
12530
12531       case MoveNumberOne:
12532         if (lastLoadGameStart == GNUChessGame) {
12533             /* GNUChessGames have numbers, but they aren't move numbers */
12534             if (appData.debugMode)
12535               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12536                       yy_text, (int) moveType);
12537             return LoadGameOneMove(EndOfFile); /* tail recursion */
12538         }
12539         /* else fall thru */
12540
12541       case XBoardGame:
12542       case GNUChessGame:
12543       case PGNTag:
12544         /* Reached start of next game in file */
12545         if (appData.debugMode)
12546           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12547         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12548           case MT_NONE:
12549           case MT_CHECK:
12550             break;
12551           case MT_CHECKMATE:
12552           case MT_STAINMATE:
12553             if (WhiteOnMove(currentMove)) {
12554                 GameEnds(BlackWins, "Black mates", GE_FILE);
12555             } else {
12556                 GameEnds(WhiteWins, "White mates", GE_FILE);
12557             }
12558             break;
12559           case MT_STALEMATE:
12560             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12561             break;
12562         }
12563         done = TRUE;
12564         break;
12565
12566       case PositionDiagram:     /* should not happen; ignore */
12567       case ElapsedTime:         /* ignore */
12568       case NAG:                 /* ignore */
12569         if (appData.debugMode)
12570           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12571                   yy_text, (int) moveType);
12572         return LoadGameOneMove(EndOfFile); /* tail recursion */
12573
12574       case IllegalMove:
12575         if (appData.testLegality) {
12576             if (appData.debugMode)
12577               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12578             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12579                     (forwardMostMove / 2) + 1,
12580                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12581             DisplayError(move, 0);
12582             done = TRUE;
12583         } else {
12584             if (appData.debugMode)
12585               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12586                       yy_text, currentMoveString);
12587             if(currentMoveString[1] == '@') {
12588                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12589                 fromY = DROP_RANK;
12590             } else {
12591                 fromX = currentMoveString[0] - AAA;
12592                 fromY = currentMoveString[1] - ONE;
12593             }
12594             toX = currentMoveString[2] - AAA;
12595             toY = currentMoveString[3] - ONE;
12596             promoChar = currentMoveString[4];
12597         }
12598         break;
12599
12600       case AmbiguousMove:
12601         if (appData.debugMode)
12602           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12603         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12604                 (forwardMostMove / 2) + 1,
12605                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12606         DisplayError(move, 0);
12607         done = TRUE;
12608         break;
12609
12610       default:
12611       case ImpossibleMove:
12612         if (appData.debugMode)
12613           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12614         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12615                 (forwardMostMove / 2) + 1,
12616                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12617         DisplayError(move, 0);
12618         done = TRUE;
12619         break;
12620     }
12621
12622     if (done) {
12623         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12624             DrawPosition(FALSE, boards[currentMove]);
12625             DisplayBothClocks();
12626             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12627               DisplayComment(currentMove - 1, commentList[currentMove]);
12628         }
12629         (void) StopLoadGameTimer();
12630         gameFileFP = NULL;
12631         cmailOldMove = forwardMostMove;
12632         return FALSE;
12633     } else {
12634         /* currentMoveString is set as a side-effect of yylex */
12635
12636         thinkOutput[0] = NULLCHAR;
12637         MakeMove(fromX, fromY, toX, toY, promoChar);
12638         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12639         currentMove = forwardMostMove;
12640         return TRUE;
12641     }
12642 }
12643
12644 /* Load the nth game from the given file */
12645 int
12646 LoadGameFromFile (char *filename, int n, char *title, int useList)
12647 {
12648     FILE *f;
12649     char buf[MSG_SIZ];
12650
12651     if (strcmp(filename, "-") == 0) {
12652         f = stdin;
12653         title = "stdin";
12654     } else {
12655         f = fopen(filename, "rb");
12656         if (f == NULL) {
12657           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12658             DisplayError(buf, errno);
12659             return FALSE;
12660         }
12661     }
12662     if (fseek(f, 0, 0) == -1) {
12663         /* f is not seekable; probably a pipe */
12664         useList = FALSE;
12665     }
12666     if (useList && n == 0) {
12667         int error = GameListBuild(f);
12668         if (error) {
12669             DisplayError(_("Cannot build game list"), error);
12670         } else if (!ListEmpty(&gameList) &&
12671                    ((ListGame *) gameList.tailPred)->number > 1) {
12672             GameListPopUp(f, title);
12673             return TRUE;
12674         }
12675         GameListDestroy();
12676         n = 1;
12677     }
12678     if (n == 0) n = 1;
12679     return LoadGame(f, n, title, FALSE);
12680 }
12681
12682
12683 void
12684 MakeRegisteredMove ()
12685 {
12686     int fromX, fromY, toX, toY;
12687     char promoChar;
12688     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12689         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12690           case CMAIL_MOVE:
12691           case CMAIL_DRAW:
12692             if (appData.debugMode)
12693               fprintf(debugFP, "Restoring %s for game %d\n",
12694                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12695
12696             thinkOutput[0] = NULLCHAR;
12697             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12698             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12699             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12700             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12701             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12702             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12703             MakeMove(fromX, fromY, toX, toY, promoChar);
12704             ShowMove(fromX, fromY, toX, toY);
12705
12706             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12707               case MT_NONE:
12708               case MT_CHECK:
12709                 break;
12710
12711               case MT_CHECKMATE:
12712               case MT_STAINMATE:
12713                 if (WhiteOnMove(currentMove)) {
12714                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12715                 } else {
12716                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12717                 }
12718                 break;
12719
12720               case MT_STALEMATE:
12721                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12722                 break;
12723             }
12724
12725             break;
12726
12727           case CMAIL_RESIGN:
12728             if (WhiteOnMove(currentMove)) {
12729                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12730             } else {
12731                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12732             }
12733             break;
12734
12735           case CMAIL_ACCEPT:
12736             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12737             break;
12738
12739           default:
12740             break;
12741         }
12742     }
12743
12744     return;
12745 }
12746
12747 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12748 int
12749 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12750 {
12751     int retVal;
12752
12753     if (gameNumber > nCmailGames) {
12754         DisplayError(_("No more games in this message"), 0);
12755         return FALSE;
12756     }
12757     if (f == lastLoadGameFP) {
12758         int offset = gameNumber - lastLoadGameNumber;
12759         if (offset == 0) {
12760             cmailMsg[0] = NULLCHAR;
12761             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12762                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12763                 nCmailMovesRegistered--;
12764             }
12765             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12766             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12767                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12768             }
12769         } else {
12770             if (! RegisterMove()) return FALSE;
12771         }
12772     }
12773
12774     retVal = LoadGame(f, gameNumber, title, useList);
12775
12776     /* Make move registered during previous look at this game, if any */
12777     MakeRegisteredMove();
12778
12779     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12780         commentList[currentMove]
12781           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12782         DisplayComment(currentMove - 1, commentList[currentMove]);
12783     }
12784
12785     return retVal;
12786 }
12787
12788 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12789 int
12790 ReloadGame (int offset)
12791 {
12792     int gameNumber = lastLoadGameNumber + offset;
12793     if (lastLoadGameFP == NULL) {
12794         DisplayError(_("No game has been loaded yet"), 0);
12795         return FALSE;
12796     }
12797     if (gameNumber <= 0) {
12798         DisplayError(_("Can't back up any further"), 0);
12799         return FALSE;
12800     }
12801     if (cmailMsgLoaded) {
12802         return CmailLoadGame(lastLoadGameFP, gameNumber,
12803                              lastLoadGameTitle, lastLoadGameUseList);
12804     } else {
12805         return LoadGame(lastLoadGameFP, gameNumber,
12806                         lastLoadGameTitle, lastLoadGameUseList);
12807     }
12808 }
12809
12810 int keys[EmptySquare+1];
12811
12812 int
12813 PositionMatches (Board b1, Board b2)
12814 {
12815     int r, f, sum=0;
12816     switch(appData.searchMode) {
12817         case 1: return CompareWithRights(b1, b2);
12818         case 2:
12819             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12820                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12821             }
12822             return TRUE;
12823         case 3:
12824             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12825               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12826                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12827             }
12828             return sum==0;
12829         case 4:
12830             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12831                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12832             }
12833             return sum==0;
12834     }
12835     return TRUE;
12836 }
12837
12838 #define Q_PROMO  4
12839 #define Q_EP     3
12840 #define Q_BCASTL 2
12841 #define Q_WCASTL 1
12842
12843 int pieceList[256], quickBoard[256];
12844 ChessSquare pieceType[256] = { EmptySquare };
12845 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12846 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12847 int soughtTotal, turn;
12848 Boolean epOK, flipSearch;
12849
12850 typedef struct {
12851     unsigned char piece, to;
12852 } Move;
12853
12854 #define DSIZE (250000)
12855
12856 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12857 Move *moveDatabase = initialSpace;
12858 unsigned int movePtr, dataSize = DSIZE;
12859
12860 int
12861 MakePieceList (Board board, int *counts)
12862 {
12863     int r, f, n=Q_PROMO, total=0;
12864     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12865     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12866         int sq = f + (r<<4);
12867         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12868             quickBoard[sq] = ++n;
12869             pieceList[n] = sq;
12870             pieceType[n] = board[r][f];
12871             counts[board[r][f]]++;
12872             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12873             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12874             total++;
12875         }
12876     }
12877     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12878     return total;
12879 }
12880
12881 void
12882 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12883 {
12884     int sq = fromX + (fromY<<4);
12885     int piece = quickBoard[sq], rook;
12886     quickBoard[sq] = 0;
12887     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12888     if(piece == pieceList[1] && fromY == toY) {
12889       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12890         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12891         moveDatabase[movePtr++].piece = Q_WCASTL;
12892         quickBoard[sq] = piece;
12893         piece = quickBoard[from]; quickBoard[from] = 0;
12894         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12895       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12896         quickBoard[sq] = 0; // remove Rook
12897         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12898         moveDatabase[movePtr++].piece = Q_WCASTL;
12899         quickBoard[sq] = pieceList[1]; // put King
12900         piece = rook;
12901         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12902       }
12903     } else
12904     if(piece == pieceList[2] && fromY == toY) {
12905       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12906         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12907         moveDatabase[movePtr++].piece = Q_BCASTL;
12908         quickBoard[sq] = piece;
12909         piece = quickBoard[from]; quickBoard[from] = 0;
12910         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12911       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12912         quickBoard[sq] = 0; // remove Rook
12913         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12914         moveDatabase[movePtr++].piece = Q_BCASTL;
12915         quickBoard[sq] = pieceList[2]; // put King
12916         piece = rook;
12917         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12918       }
12919     } else
12920     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12921         quickBoard[(fromY<<4)+toX] = 0;
12922         moveDatabase[movePtr].piece = Q_EP;
12923         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12924         moveDatabase[movePtr].to = sq;
12925     } else
12926     if(promoPiece != pieceType[piece]) {
12927         moveDatabase[movePtr++].piece = Q_PROMO;
12928         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12929     }
12930     moveDatabase[movePtr].piece = piece;
12931     quickBoard[sq] = piece;
12932     movePtr++;
12933 }
12934
12935 int
12936 PackGame (Board board)
12937 {
12938     Move *newSpace = NULL;
12939     moveDatabase[movePtr].piece = 0; // terminate previous game
12940     if(movePtr > dataSize) {
12941         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12942         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12943         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12944         if(newSpace) {
12945             int i;
12946             Move *p = moveDatabase, *q = newSpace;
12947             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12948             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12949             moveDatabase = newSpace;
12950         } else { // calloc failed, we must be out of memory. Too bad...
12951             dataSize = 0; // prevent calloc events for all subsequent games
12952             return 0;     // and signal this one isn't cached
12953         }
12954     }
12955     movePtr++;
12956     MakePieceList(board, counts);
12957     return movePtr;
12958 }
12959
12960 int
12961 QuickCompare (Board board, int *minCounts, int *maxCounts)
12962 {   // compare according to search mode
12963     int r, f;
12964     switch(appData.searchMode)
12965     {
12966       case 1: // exact position match
12967         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12968         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12969             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12970         }
12971         break;
12972       case 2: // can have extra material on empty squares
12973         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12974             if(board[r][f] == EmptySquare) continue;
12975             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12976         }
12977         break;
12978       case 3: // material with exact Pawn structure
12979         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12980             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12981             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12982         } // fall through to material comparison
12983       case 4: // exact material
12984         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12985         break;
12986       case 6: // material range with given imbalance
12987         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12988         // fall through to range comparison
12989       case 5: // material range
12990         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12991     }
12992     return TRUE;
12993 }
12994
12995 int
12996 QuickScan (Board board, Move *move)
12997 {   // reconstruct game,and compare all positions in it
12998     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12999     do {
13000         int piece = move->piece;
13001         int to = move->to, from = pieceList[piece];
13002         if(found < 0) { // if already found just scan to game end for final piece count
13003           if(QuickCompare(soughtBoard, minSought, maxSought) ||
13004            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
13005            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
13006                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
13007             ) {
13008             static int lastCounts[EmptySquare+1];
13009             int i;
13010             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
13011             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
13012           } else stretch = 0;
13013           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13014           if(found >= 0 && !appData.minPieces) return found;
13015         }
13016         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13017           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13018           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13019             piece = (++move)->piece;
13020             from = pieceList[piece];
13021             counts[pieceType[piece]]--;
13022             pieceType[piece] = (ChessSquare) move->to;
13023             counts[move->to]++;
13024           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13025             counts[pieceType[quickBoard[to]]]--;
13026             quickBoard[to] = 0; total--;
13027             move++;
13028             continue;
13029           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13030             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13031             from  = pieceList[piece]; // so this must be King
13032             quickBoard[from] = 0;
13033             pieceList[piece] = to;
13034             from = pieceList[(++move)->piece]; // for FRC this has to be done here
13035             quickBoard[from] = 0; // rook
13036             quickBoard[to] = piece;
13037             to = move->to; piece = move->piece;
13038             goto aftercastle;
13039           }
13040         }
13041         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13042         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13043         quickBoard[from] = 0;
13044       aftercastle:
13045         quickBoard[to] = piece;
13046         pieceList[piece] = to;
13047         cnt++; turn ^= 3;
13048         move++;
13049     } while(1);
13050 }
13051
13052 void
13053 InitSearch ()
13054 {
13055     int r, f;
13056     flipSearch = FALSE;
13057     CopyBoard(soughtBoard, boards[currentMove]);
13058     soughtTotal = MakePieceList(soughtBoard, maxSought);
13059     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13060     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13061     CopyBoard(reverseBoard, boards[currentMove]);
13062     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13063         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13064         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13065         reverseBoard[r][f] = piece;
13066     }
13067     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13068     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13069     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13070                  || (boards[currentMove][CASTLING][2] == NoRights ||
13071                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13072                  && (boards[currentMove][CASTLING][5] == NoRights ||
13073                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13074       ) {
13075         flipSearch = TRUE;
13076         CopyBoard(flipBoard, soughtBoard);
13077         CopyBoard(rotateBoard, reverseBoard);
13078         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13079             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13080             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13081         }
13082     }
13083     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13084     if(appData.searchMode >= 5) {
13085         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13086         MakePieceList(soughtBoard, minSought);
13087         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13088     }
13089     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13090         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13091 }
13092
13093 GameInfo dummyInfo;
13094 static int creatingBook;
13095
13096 int
13097 GameContainsPosition (FILE *f, ListGame *lg)
13098 {
13099     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13100     int fromX, fromY, toX, toY;
13101     char promoChar;
13102     static int initDone=FALSE;
13103
13104     // weed out games based on numerical tag comparison
13105     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13106     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13107     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13108     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13109     if(!initDone) {
13110         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13111         initDone = TRUE;
13112     }
13113     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13114     else CopyBoard(boards[scratch], initialPosition); // default start position
13115     if(lg->moves) {
13116         turn = btm + 1;
13117         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13118         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13119     }
13120     if(btm) plyNr++;
13121     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13122     fseek(f, lg->offset, 0);
13123     yynewfile(f);
13124     while(1) {
13125         yyboardindex = scratch;
13126         quickFlag = plyNr+1;
13127         next = Myylex();
13128         quickFlag = 0;
13129         switch(next) {
13130             case PGNTag:
13131                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13132             default:
13133                 continue;
13134
13135             case XBoardGame:
13136             case GNUChessGame:
13137                 if(plyNr) return -1; // after we have seen moves, this is for new game
13138               continue;
13139
13140             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13141             case ImpossibleMove:
13142             case WhiteWins: // game ends here with these four
13143             case BlackWins:
13144             case GameIsDrawn:
13145             case GameUnfinished:
13146                 return -1;
13147
13148             case IllegalMove:
13149                 if(appData.testLegality) return -1;
13150             case WhiteCapturesEnPassant:
13151             case BlackCapturesEnPassant:
13152             case WhitePromotion:
13153             case BlackPromotion:
13154             case WhiteNonPromotion:
13155             case BlackNonPromotion:
13156             case NormalMove:
13157             case FirstLeg:
13158             case WhiteKingSideCastle:
13159             case WhiteQueenSideCastle:
13160             case BlackKingSideCastle:
13161             case BlackQueenSideCastle:
13162             case WhiteKingSideCastleWild:
13163             case WhiteQueenSideCastleWild:
13164             case BlackKingSideCastleWild:
13165             case BlackQueenSideCastleWild:
13166             case WhiteHSideCastleFR:
13167             case WhiteASideCastleFR:
13168             case BlackHSideCastleFR:
13169             case BlackASideCastleFR:
13170                 fromX = currentMoveString[0] - AAA;
13171                 fromY = currentMoveString[1] - ONE;
13172                 toX = currentMoveString[2] - AAA;
13173                 toY = currentMoveString[3] - ONE;
13174                 promoChar = currentMoveString[4];
13175                 break;
13176             case WhiteDrop:
13177             case BlackDrop:
13178                 fromX = next == WhiteDrop ?
13179                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13180                   (int) CharToPiece(ToLower(currentMoveString[0]));
13181                 fromY = DROP_RANK;
13182                 toX = currentMoveString[2] - AAA;
13183                 toY = currentMoveString[3] - ONE;
13184                 promoChar = 0;
13185                 break;
13186         }
13187         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13188         plyNr++;
13189         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13190         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13191         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13192         if(appData.findMirror) {
13193             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13194             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13195         }
13196     }
13197 }
13198
13199 /* Load the nth game from open file f */
13200 int
13201 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13202 {
13203     ChessMove cm;
13204     char buf[MSG_SIZ];
13205     int gn = gameNumber;
13206     ListGame *lg = NULL;
13207     int numPGNTags = 0, i;
13208     int err, pos = -1;
13209     GameMode oldGameMode;
13210     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13211     char oldName[MSG_SIZ];
13212
13213     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13214
13215     if (appData.debugMode)
13216         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13217
13218     if (gameMode == Training )
13219         SetTrainingModeOff();
13220
13221     oldGameMode = gameMode;
13222     if (gameMode != BeginningOfGame) {
13223       Reset(FALSE, TRUE);
13224     }
13225     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13226
13227     gameFileFP = f;
13228     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13229         fclose(lastLoadGameFP);
13230     }
13231
13232     if (useList) {
13233         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13234
13235         if (lg) {
13236             fseek(f, lg->offset, 0);
13237             GameListHighlight(gameNumber);
13238             pos = lg->position;
13239             gn = 1;
13240         }
13241         else {
13242             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13243               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13244             else
13245             DisplayError(_("Game number out of range"), 0);
13246             return FALSE;
13247         }
13248     } else {
13249         GameListDestroy();
13250         if (fseek(f, 0, 0) == -1) {
13251             if (f == lastLoadGameFP ?
13252                 gameNumber == lastLoadGameNumber + 1 :
13253                 gameNumber == 1) {
13254                 gn = 1;
13255             } else {
13256                 DisplayError(_("Can't seek on game file"), 0);
13257                 return FALSE;
13258             }
13259         }
13260     }
13261     lastLoadGameFP = f;
13262     lastLoadGameNumber = gameNumber;
13263     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13264     lastLoadGameUseList = useList;
13265
13266     yynewfile(f);
13267
13268     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13269       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13270                 lg->gameInfo.black);
13271             DisplayTitle(buf);
13272     } else if (*title != NULLCHAR) {
13273         if (gameNumber > 1) {
13274           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13275             DisplayTitle(buf);
13276         } else {
13277             DisplayTitle(title);
13278         }
13279     }
13280
13281     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13282         gameMode = PlayFromGameFile;
13283         ModeHighlight();
13284     }
13285
13286     currentMove = forwardMostMove = backwardMostMove = 0;
13287     CopyBoard(boards[0], initialPosition);
13288     StopClocks();
13289
13290     /*
13291      * Skip the first gn-1 games in the file.
13292      * Also skip over anything that precedes an identifiable
13293      * start of game marker, to avoid being confused by
13294      * garbage at the start of the file.  Currently
13295      * recognized start of game markers are the move number "1",
13296      * the pattern "gnuchess .* game", the pattern
13297      * "^[#;%] [^ ]* game file", and a PGN tag block.
13298      * A game that starts with one of the latter two patterns
13299      * will also have a move number 1, possibly
13300      * following a position diagram.
13301      * 5-4-02: Let's try being more lenient and allowing a game to
13302      * start with an unnumbered move.  Does that break anything?
13303      */
13304     cm = lastLoadGameStart = EndOfFile;
13305     while (gn > 0) {
13306         yyboardindex = forwardMostMove;
13307         cm = (ChessMove) Myylex();
13308         switch (cm) {
13309           case EndOfFile:
13310             if (cmailMsgLoaded) {
13311                 nCmailGames = CMAIL_MAX_GAMES - gn;
13312             } else {
13313                 Reset(TRUE, TRUE);
13314                 DisplayError(_("Game not found in file"), 0);
13315             }
13316             return FALSE;
13317
13318           case GNUChessGame:
13319           case XBoardGame:
13320             gn--;
13321             lastLoadGameStart = cm;
13322             break;
13323
13324           case MoveNumberOne:
13325             switch (lastLoadGameStart) {
13326               case GNUChessGame:
13327               case XBoardGame:
13328               case PGNTag:
13329                 break;
13330               case MoveNumberOne:
13331               case EndOfFile:
13332                 gn--;           /* count this game */
13333                 lastLoadGameStart = cm;
13334                 break;
13335               default:
13336                 /* impossible */
13337                 break;
13338             }
13339             break;
13340
13341           case PGNTag:
13342             switch (lastLoadGameStart) {
13343               case GNUChessGame:
13344               case PGNTag:
13345               case MoveNumberOne:
13346               case EndOfFile:
13347                 gn--;           /* count this game */
13348                 lastLoadGameStart = cm;
13349                 break;
13350               case XBoardGame:
13351                 lastLoadGameStart = cm; /* game counted already */
13352                 break;
13353               default:
13354                 /* impossible */
13355                 break;
13356             }
13357             if (gn > 0) {
13358                 do {
13359                     yyboardindex = forwardMostMove;
13360                     cm = (ChessMove) Myylex();
13361                 } while (cm == PGNTag || cm == Comment);
13362             }
13363             break;
13364
13365           case WhiteWins:
13366           case BlackWins:
13367           case GameIsDrawn:
13368             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13369                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13370                     != CMAIL_OLD_RESULT) {
13371                     nCmailResults ++ ;
13372                     cmailResult[  CMAIL_MAX_GAMES
13373                                 - gn - 1] = CMAIL_OLD_RESULT;
13374                 }
13375             }
13376             break;
13377
13378           case NormalMove:
13379           case FirstLeg:
13380             /* Only a NormalMove can be at the start of a game
13381              * without a position diagram. */
13382             if (lastLoadGameStart == EndOfFile ) {
13383               gn--;
13384               lastLoadGameStart = MoveNumberOne;
13385             }
13386             break;
13387
13388           default:
13389             break;
13390         }
13391     }
13392
13393     if (appData.debugMode)
13394       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13395
13396     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13397
13398     if (cm == XBoardGame) {
13399         /* Skip any header junk before position diagram and/or move 1 */
13400         for (;;) {
13401             yyboardindex = forwardMostMove;
13402             cm = (ChessMove) Myylex();
13403
13404             if (cm == EndOfFile ||
13405                 cm == GNUChessGame || cm == XBoardGame) {
13406                 /* Empty game; pretend end-of-file and handle later */
13407                 cm = EndOfFile;
13408                 break;
13409             }
13410
13411             if (cm == MoveNumberOne || cm == PositionDiagram ||
13412                 cm == PGNTag || cm == Comment)
13413               break;
13414         }
13415     } else if (cm == GNUChessGame) {
13416         if (gameInfo.event != NULL) {
13417             free(gameInfo.event);
13418         }
13419         gameInfo.event = StrSave(yy_text);
13420     }
13421
13422     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13423     while (cm == PGNTag) {
13424         if (appData.debugMode)
13425           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13426         err = ParsePGNTag(yy_text, &gameInfo);
13427         if (!err) numPGNTags++;
13428
13429         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13430         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13431             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13432             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13433             InitPosition(TRUE);
13434             oldVariant = gameInfo.variant;
13435             if (appData.debugMode)
13436               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13437         }
13438
13439
13440         if (gameInfo.fen != NULL) {
13441           Board initial_position;
13442           startedFromSetupPosition = TRUE;
13443           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13444             Reset(TRUE, TRUE);
13445             DisplayError(_("Bad FEN position in file"), 0);
13446             return FALSE;
13447           }
13448           CopyBoard(boards[0], initial_position);
13449           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13450             CopyBoard(initialPosition, initial_position);
13451           if (blackPlaysFirst) {
13452             currentMove = forwardMostMove = backwardMostMove = 1;
13453             CopyBoard(boards[1], initial_position);
13454             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13455             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13456             timeRemaining[0][1] = whiteTimeRemaining;
13457             timeRemaining[1][1] = blackTimeRemaining;
13458             if (commentList[0] != NULL) {
13459               commentList[1] = commentList[0];
13460               commentList[0] = NULL;
13461             }
13462           } else {
13463             currentMove = forwardMostMove = backwardMostMove = 0;
13464           }
13465           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13466           {   int i;
13467               initialRulePlies = FENrulePlies;
13468               for( i=0; i< nrCastlingRights; i++ )
13469                   initialRights[i] = initial_position[CASTLING][i];
13470           }
13471           yyboardindex = forwardMostMove;
13472           free(gameInfo.fen);
13473           gameInfo.fen = NULL;
13474         }
13475
13476         yyboardindex = forwardMostMove;
13477         cm = (ChessMove) Myylex();
13478
13479         /* Handle comments interspersed among the tags */
13480         while (cm == Comment) {
13481             char *p;
13482             if (appData.debugMode)
13483               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13484             p = yy_text;
13485             AppendComment(currentMove, p, FALSE);
13486             yyboardindex = forwardMostMove;
13487             cm = (ChessMove) Myylex();
13488         }
13489     }
13490
13491     /* don't rely on existence of Event tag since if game was
13492      * pasted from clipboard the Event tag may not exist
13493      */
13494     if (numPGNTags > 0){
13495         char *tags;
13496         if (gameInfo.variant == VariantNormal) {
13497           VariantClass v = StringToVariant(gameInfo.event);
13498           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13499           if(v < VariantShogi) gameInfo.variant = v;
13500         }
13501         if (!matchMode) {
13502           if( appData.autoDisplayTags ) {
13503             tags = PGNTags(&gameInfo);
13504             TagsPopUp(tags, CmailMsg());
13505             free(tags);
13506           }
13507         }
13508     } else {
13509         /* Make something up, but don't display it now */
13510         SetGameInfo();
13511         TagsPopDown();
13512     }
13513
13514     if (cm == PositionDiagram) {
13515         int i, j;
13516         char *p;
13517         Board initial_position;
13518
13519         if (appData.debugMode)
13520           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13521
13522         if (!startedFromSetupPosition) {
13523             p = yy_text;
13524             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13525               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13526                 switch (*p) {
13527                   case '{':
13528                   case '[':
13529                   case '-':
13530                   case ' ':
13531                   case '\t':
13532                   case '\n':
13533                   case '\r':
13534                     break;
13535                   default:
13536                     initial_position[i][j++] = CharToPiece(*p);
13537                     break;
13538                 }
13539             while (*p == ' ' || *p == '\t' ||
13540                    *p == '\n' || *p == '\r') p++;
13541
13542             if (strncmp(p, "black", strlen("black"))==0)
13543               blackPlaysFirst = TRUE;
13544             else
13545               blackPlaysFirst = FALSE;
13546             startedFromSetupPosition = TRUE;
13547
13548             CopyBoard(boards[0], initial_position);
13549             if (blackPlaysFirst) {
13550                 currentMove = forwardMostMove = backwardMostMove = 1;
13551                 CopyBoard(boards[1], initial_position);
13552                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13553                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13554                 timeRemaining[0][1] = whiteTimeRemaining;
13555                 timeRemaining[1][1] = blackTimeRemaining;
13556                 if (commentList[0] != NULL) {
13557                     commentList[1] = commentList[0];
13558                     commentList[0] = NULL;
13559                 }
13560             } else {
13561                 currentMove = forwardMostMove = backwardMostMove = 0;
13562             }
13563         }
13564         yyboardindex = forwardMostMove;
13565         cm = (ChessMove) Myylex();
13566     }
13567
13568   if(!creatingBook) {
13569     if (first.pr == NoProc) {
13570         StartChessProgram(&first);
13571     }
13572     InitChessProgram(&first, FALSE);
13573     if(gameInfo.variant == VariantUnknown && *oldName) {
13574         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13575         gameInfo.variant = v;
13576     }
13577     SendToProgram("force\n", &first);
13578     if (startedFromSetupPosition) {
13579         SendBoard(&first, forwardMostMove);
13580     if (appData.debugMode) {
13581         fprintf(debugFP, "Load Game\n");
13582     }
13583         DisplayBothClocks();
13584     }
13585   }
13586
13587     /* [HGM] server: flag to write setup moves in broadcast file as one */
13588     loadFlag = appData.suppressLoadMoves;
13589
13590     while (cm == Comment) {
13591         char *p;
13592         if (appData.debugMode)
13593           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13594         p = yy_text;
13595         AppendComment(currentMove, p, FALSE);
13596         yyboardindex = forwardMostMove;
13597         cm = (ChessMove) Myylex();
13598     }
13599
13600     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13601         cm == WhiteWins || cm == BlackWins ||
13602         cm == GameIsDrawn || cm == GameUnfinished) {
13603         DisplayMessage("", _("No moves in game"));
13604         if (cmailMsgLoaded) {
13605             if (appData.debugMode)
13606               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13607             ClearHighlights();
13608             flipView = FALSE;
13609         }
13610         DrawPosition(FALSE, boards[currentMove]);
13611         DisplayBothClocks();
13612         gameMode = EditGame;
13613         ModeHighlight();
13614         gameFileFP = NULL;
13615         cmailOldMove = 0;
13616         return TRUE;
13617     }
13618
13619     // [HGM] PV info: routine tests if comment empty
13620     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13621         DisplayComment(currentMove - 1, commentList[currentMove]);
13622     }
13623     if (!matchMode && appData.timeDelay != 0)
13624       DrawPosition(FALSE, boards[currentMove]);
13625
13626     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13627       programStats.ok_to_send = 1;
13628     }
13629
13630     /* if the first token after the PGN tags is a move
13631      * and not move number 1, retrieve it from the parser
13632      */
13633     if (cm != MoveNumberOne)
13634         LoadGameOneMove(cm);
13635
13636     /* load the remaining moves from the file */
13637     while (LoadGameOneMove(EndOfFile)) {
13638       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13639       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13640     }
13641
13642     /* rewind to the start of the game */
13643     currentMove = backwardMostMove;
13644
13645     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13646
13647     if (oldGameMode == AnalyzeFile) {
13648       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13649       AnalyzeFileEvent();
13650     } else
13651     if (oldGameMode == AnalyzeMode) {
13652       AnalyzeFileEvent();
13653     }
13654
13655     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13656         long int w, b; // [HGM] adjourn: restore saved clock times
13657         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13658         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13659             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13660             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13661         }
13662     }
13663
13664     if(creatingBook) return TRUE;
13665     if (!matchMode && pos > 0) {
13666         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13667     } else
13668     if (matchMode || appData.timeDelay == 0) {
13669       ToEndEvent();
13670     } else if (appData.timeDelay > 0) {
13671       AutoPlayGameLoop();
13672     }
13673
13674     if (appData.debugMode)
13675         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13676
13677     loadFlag = 0; /* [HGM] true game starts */
13678     return TRUE;
13679 }
13680
13681 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13682 int
13683 ReloadPosition (int offset)
13684 {
13685     int positionNumber = lastLoadPositionNumber + offset;
13686     if (lastLoadPositionFP == NULL) {
13687         DisplayError(_("No position has been loaded yet"), 0);
13688         return FALSE;
13689     }
13690     if (positionNumber <= 0) {
13691         DisplayError(_("Can't back up any further"), 0);
13692         return FALSE;
13693     }
13694     return LoadPosition(lastLoadPositionFP, positionNumber,
13695                         lastLoadPositionTitle);
13696 }
13697
13698 /* Load the nth position from the given file */
13699 int
13700 LoadPositionFromFile (char *filename, int n, char *title)
13701 {
13702     FILE *f;
13703     char buf[MSG_SIZ];
13704
13705     if (strcmp(filename, "-") == 0) {
13706         return LoadPosition(stdin, n, "stdin");
13707     } else {
13708         f = fopen(filename, "rb");
13709         if (f == NULL) {
13710             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13711             DisplayError(buf, errno);
13712             return FALSE;
13713         } else {
13714             return LoadPosition(f, n, title);
13715         }
13716     }
13717 }
13718
13719 /* Load the nth position from the given open file, and close it */
13720 int
13721 LoadPosition (FILE *f, int positionNumber, char *title)
13722 {
13723     char *p, line[MSG_SIZ];
13724     Board initial_position;
13725     int i, j, fenMode, pn;
13726
13727     if (gameMode == Training )
13728         SetTrainingModeOff();
13729
13730     if (gameMode != BeginningOfGame) {
13731         Reset(FALSE, TRUE);
13732     }
13733     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13734         fclose(lastLoadPositionFP);
13735     }
13736     if (positionNumber == 0) positionNumber = 1;
13737     lastLoadPositionFP = f;
13738     lastLoadPositionNumber = positionNumber;
13739     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13740     if (first.pr == NoProc && !appData.noChessProgram) {
13741       StartChessProgram(&first);
13742       InitChessProgram(&first, FALSE);
13743     }
13744     pn = positionNumber;
13745     if (positionNumber < 0) {
13746         /* Negative position number means to seek to that byte offset */
13747         if (fseek(f, -positionNumber, 0) == -1) {
13748             DisplayError(_("Can't seek on position file"), 0);
13749             return FALSE;
13750         };
13751         pn = 1;
13752     } else {
13753         if (fseek(f, 0, 0) == -1) {
13754             if (f == lastLoadPositionFP ?
13755                 positionNumber == lastLoadPositionNumber + 1 :
13756                 positionNumber == 1) {
13757                 pn = 1;
13758             } else {
13759                 DisplayError(_("Can't seek on position file"), 0);
13760                 return FALSE;
13761             }
13762         }
13763     }
13764     /* See if this file is FEN or old-style xboard */
13765     if (fgets(line, MSG_SIZ, f) == NULL) {
13766         DisplayError(_("Position not found in file"), 0);
13767         return FALSE;
13768     }
13769     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13770     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13771
13772     if (pn >= 2) {
13773         if (fenMode || line[0] == '#') pn--;
13774         while (pn > 0) {
13775             /* skip positions before number pn */
13776             if (fgets(line, MSG_SIZ, f) == NULL) {
13777                 Reset(TRUE, TRUE);
13778                 DisplayError(_("Position not found in file"), 0);
13779                 return FALSE;
13780             }
13781             if (fenMode || line[0] == '#') pn--;
13782         }
13783     }
13784
13785     if (fenMode) {
13786         char *p;
13787         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13788             DisplayError(_("Bad FEN position in file"), 0);
13789             return FALSE;
13790         }
13791         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13792             sscanf(p+4, "%[^;]", bestMove);
13793         } else *bestMove = NULLCHAR;
13794         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13795             sscanf(p+4, "%[^;]", avoidMove);
13796         } else *avoidMove = NULLCHAR;
13797     } else {
13798         (void) fgets(line, MSG_SIZ, f);
13799         (void) fgets(line, MSG_SIZ, f);
13800
13801         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13802             (void) fgets(line, MSG_SIZ, f);
13803             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13804                 if (*p == ' ')
13805                   continue;
13806                 initial_position[i][j++] = CharToPiece(*p);
13807             }
13808         }
13809
13810         blackPlaysFirst = FALSE;
13811         if (!feof(f)) {
13812             (void) fgets(line, MSG_SIZ, f);
13813             if (strncmp(line, "black", strlen("black"))==0)
13814               blackPlaysFirst = TRUE;
13815         }
13816     }
13817     startedFromSetupPosition = TRUE;
13818
13819     CopyBoard(boards[0], initial_position);
13820     if (blackPlaysFirst) {
13821         currentMove = forwardMostMove = backwardMostMove = 1;
13822         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13823         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13824         CopyBoard(boards[1], initial_position);
13825         DisplayMessage("", _("Black to play"));
13826     } else {
13827         currentMove = forwardMostMove = backwardMostMove = 0;
13828         DisplayMessage("", _("White to play"));
13829     }
13830     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13831     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13832         SendToProgram("force\n", &first);
13833         SendBoard(&first, forwardMostMove);
13834     }
13835     if (appData.debugMode) {
13836 int i, j;
13837   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13838   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13839         fprintf(debugFP, "Load Position\n");
13840     }
13841
13842     if (positionNumber > 1) {
13843       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13844         DisplayTitle(line);
13845     } else {
13846         DisplayTitle(title);
13847     }
13848     gameMode = EditGame;
13849     ModeHighlight();
13850     ResetClocks();
13851     timeRemaining[0][1] = whiteTimeRemaining;
13852     timeRemaining[1][1] = blackTimeRemaining;
13853     DrawPosition(FALSE, boards[currentMove]);
13854
13855     return TRUE;
13856 }
13857
13858
13859 void
13860 CopyPlayerNameIntoFileName (char **dest, char *src)
13861 {
13862     while (*src != NULLCHAR && *src != ',') {
13863         if (*src == ' ') {
13864             *(*dest)++ = '_';
13865             src++;
13866         } else {
13867             *(*dest)++ = *src++;
13868         }
13869     }
13870 }
13871
13872 char *
13873 DefaultFileName (char *ext)
13874 {
13875     static char def[MSG_SIZ];
13876     char *p;
13877
13878     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13879         p = def;
13880         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13881         *p++ = '-';
13882         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13883         *p++ = '.';
13884         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13885     } else {
13886         def[0] = NULLCHAR;
13887     }
13888     return def;
13889 }
13890
13891 /* Save the current game to the given file */
13892 int
13893 SaveGameToFile (char *filename, int append)
13894 {
13895     FILE *f;
13896     char buf[MSG_SIZ];
13897     int result, i, t,tot=0;
13898
13899     if (strcmp(filename, "-") == 0) {
13900         return SaveGame(stdout, 0, NULL);
13901     } else {
13902         for(i=0; i<10; i++) { // upto 10 tries
13903              f = fopen(filename, append ? "a" : "w");
13904              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13905              if(f || errno != 13) break;
13906              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13907              tot += t;
13908         }
13909         if (f == NULL) {
13910             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13911             DisplayError(buf, errno);
13912             return FALSE;
13913         } else {
13914             safeStrCpy(buf, lastMsg, MSG_SIZ);
13915             DisplayMessage(_("Waiting for access to save file"), "");
13916             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13917             DisplayMessage(_("Saving game"), "");
13918             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13919             result = SaveGame(f, 0, NULL);
13920             DisplayMessage(buf, "");
13921             return result;
13922         }
13923     }
13924 }
13925
13926 char *
13927 SavePart (char *str)
13928 {
13929     static char buf[MSG_SIZ];
13930     char *p;
13931
13932     p = strchr(str, ' ');
13933     if (p == NULL) return str;
13934     strncpy(buf, str, p - str);
13935     buf[p - str] = NULLCHAR;
13936     return buf;
13937 }
13938
13939 #define PGN_MAX_LINE 75
13940
13941 #define PGN_SIDE_WHITE  0
13942 #define PGN_SIDE_BLACK  1
13943
13944 static int
13945 FindFirstMoveOutOfBook (int side)
13946 {
13947     int result = -1;
13948
13949     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13950         int index = backwardMostMove;
13951         int has_book_hit = 0;
13952
13953         if( (index % 2) != side ) {
13954             index++;
13955         }
13956
13957         while( index < forwardMostMove ) {
13958             /* Check to see if engine is in book */
13959             int depth = pvInfoList[index].depth;
13960             int score = pvInfoList[index].score;
13961             int in_book = 0;
13962
13963             if( depth <= 2 ) {
13964                 in_book = 1;
13965             }
13966             else if( score == 0 && depth == 63 ) {
13967                 in_book = 1; /* Zappa */
13968             }
13969             else if( score == 2 && depth == 99 ) {
13970                 in_book = 1; /* Abrok */
13971             }
13972
13973             has_book_hit += in_book;
13974
13975             if( ! in_book ) {
13976                 result = index;
13977
13978                 break;
13979             }
13980
13981             index += 2;
13982         }
13983     }
13984
13985     return result;
13986 }
13987
13988 void
13989 GetOutOfBookInfo (char * buf)
13990 {
13991     int oob[2];
13992     int i;
13993     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13994
13995     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13996     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13997
13998     *buf = '\0';
13999
14000     if( oob[0] >= 0 || oob[1] >= 0 ) {
14001         for( i=0; i<2; i++ ) {
14002             int idx = oob[i];
14003
14004             if( idx >= 0 ) {
14005                 if( i > 0 && oob[0] >= 0 ) {
14006                     strcat( buf, "   " );
14007                 }
14008
14009                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
14010                 sprintf( buf+strlen(buf), "%s%.2f",
14011                     pvInfoList[idx].score >= 0 ? "+" : "",
14012                     pvInfoList[idx].score / 100.0 );
14013             }
14014         }
14015     }
14016 }
14017
14018 /* Save game in PGN style */
14019 static void
14020 SaveGamePGN2 (FILE *f)
14021 {
14022     int i, offset, linelen, newblock;
14023 //    char *movetext;
14024     char numtext[32];
14025     int movelen, numlen, blank;
14026     char move_buffer[100]; /* [AS] Buffer for move+PV info */
14027
14028     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14029
14030     PrintPGNTags(f, &gameInfo);
14031
14032     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14033
14034     if (backwardMostMove > 0 || startedFromSetupPosition) {
14035         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14036         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14037         fprintf(f, "\n{--------------\n");
14038         PrintPosition(f, backwardMostMove);
14039         fprintf(f, "--------------}\n");
14040         free(fen);
14041     }
14042     else {
14043         /* [AS] Out of book annotation */
14044         if( appData.saveOutOfBookInfo ) {
14045             char buf[64];
14046
14047             GetOutOfBookInfo( buf );
14048
14049             if( buf[0] != '\0' ) {
14050                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14051             }
14052         }
14053
14054         fprintf(f, "\n");
14055     }
14056
14057     i = backwardMostMove;
14058     linelen = 0;
14059     newblock = TRUE;
14060
14061     while (i < forwardMostMove) {
14062         /* Print comments preceding this move */
14063         if (commentList[i] != NULL) {
14064             if (linelen > 0) fprintf(f, "\n");
14065             fprintf(f, "%s", commentList[i]);
14066             linelen = 0;
14067             newblock = TRUE;
14068         }
14069
14070         /* Format move number */
14071         if ((i % 2) == 0)
14072           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14073         else
14074           if (newblock)
14075             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14076           else
14077             numtext[0] = NULLCHAR;
14078
14079         numlen = strlen(numtext);
14080         newblock = FALSE;
14081
14082         /* Print move number */
14083         blank = linelen > 0 && numlen > 0;
14084         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14085             fprintf(f, "\n");
14086             linelen = 0;
14087             blank = 0;
14088         }
14089         if (blank) {
14090             fprintf(f, " ");
14091             linelen++;
14092         }
14093         fprintf(f, "%s", numtext);
14094         linelen += numlen;
14095
14096         /* Get move */
14097         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14098         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14099
14100         /* Print move */
14101         blank = linelen > 0 && movelen > 0;
14102         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14103             fprintf(f, "\n");
14104             linelen = 0;
14105             blank = 0;
14106         }
14107         if (blank) {
14108             fprintf(f, " ");
14109             linelen++;
14110         }
14111         fprintf(f, "%s", move_buffer);
14112         linelen += movelen;
14113
14114         /* [AS] Add PV info if present */
14115         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14116             /* [HGM] add time */
14117             char buf[MSG_SIZ]; int seconds;
14118
14119             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14120
14121             if( seconds <= 0)
14122               buf[0] = 0;
14123             else
14124               if( seconds < 30 )
14125                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14126               else
14127                 {
14128                   seconds = (seconds + 4)/10; // round to full seconds
14129                   if( seconds < 60 )
14130                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14131                   else
14132                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14133                 }
14134
14135             if(appData.cumulativeTimePGN) {
14136                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14137             }
14138
14139             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14140                       pvInfoList[i].score >= 0 ? "+" : "",
14141                       pvInfoList[i].score / 100.0,
14142                       pvInfoList[i].depth,
14143                       buf );
14144
14145             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14146
14147             /* Print score/depth */
14148             blank = linelen > 0 && movelen > 0;
14149             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14150                 fprintf(f, "\n");
14151                 linelen = 0;
14152                 blank = 0;
14153             }
14154             if (blank) {
14155                 fprintf(f, " ");
14156                 linelen++;
14157             }
14158             fprintf(f, "%s", move_buffer);
14159             linelen += movelen;
14160         }
14161
14162         i++;
14163     }
14164
14165     /* Start a new line */
14166     if (linelen > 0) fprintf(f, "\n");
14167
14168     /* Print comments after last move */
14169     if (commentList[i] != NULL) {
14170         fprintf(f, "%s\n", commentList[i]);
14171     }
14172
14173     /* Print result */
14174     if (gameInfo.resultDetails != NULL &&
14175         gameInfo.resultDetails[0] != NULLCHAR) {
14176         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14177         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14178            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14179             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14180         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14181     } else {
14182         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14183     }
14184 }
14185
14186 /* Save game in PGN style and close the file */
14187 int
14188 SaveGamePGN (FILE *f)
14189 {
14190     SaveGamePGN2(f);
14191     fclose(f);
14192     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14193     return TRUE;
14194 }
14195
14196 /* Save game in old style and close the file */
14197 int
14198 SaveGameOldStyle (FILE *f)
14199 {
14200     int i, offset;
14201     time_t tm;
14202
14203     tm = time((time_t *) NULL);
14204
14205     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14206     PrintOpponents(f);
14207
14208     if (backwardMostMove > 0 || startedFromSetupPosition) {
14209         fprintf(f, "\n[--------------\n");
14210         PrintPosition(f, backwardMostMove);
14211         fprintf(f, "--------------]\n");
14212     } else {
14213         fprintf(f, "\n");
14214     }
14215
14216     i = backwardMostMove;
14217     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14218
14219     while (i < forwardMostMove) {
14220         if (commentList[i] != NULL) {
14221             fprintf(f, "[%s]\n", commentList[i]);
14222         }
14223
14224         if ((i % 2) == 1) {
14225             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14226             i++;
14227         } else {
14228             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14229             i++;
14230             if (commentList[i] != NULL) {
14231                 fprintf(f, "\n");
14232                 continue;
14233             }
14234             if (i >= forwardMostMove) {
14235                 fprintf(f, "\n");
14236                 break;
14237             }
14238             fprintf(f, "%s\n", parseList[i]);
14239             i++;
14240         }
14241     }
14242
14243     if (commentList[i] != NULL) {
14244         fprintf(f, "[%s]\n", commentList[i]);
14245     }
14246
14247     /* This isn't really the old style, but it's close enough */
14248     if (gameInfo.resultDetails != NULL &&
14249         gameInfo.resultDetails[0] != NULLCHAR) {
14250         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14251                 gameInfo.resultDetails);
14252     } else {
14253         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14254     }
14255
14256     fclose(f);
14257     return TRUE;
14258 }
14259
14260 /* Save the current game to open file f and close the file */
14261 int
14262 SaveGame (FILE *f, int dummy, char *dummy2)
14263 {
14264     if (gameMode == EditPosition) EditPositionDone(TRUE);
14265     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14266     if (appData.oldSaveStyle)
14267       return SaveGameOldStyle(f);
14268     else
14269       return SaveGamePGN(f);
14270 }
14271
14272 /* Save the current position to the given file */
14273 int
14274 SavePositionToFile (char *filename)
14275 {
14276     FILE *f;
14277     char buf[MSG_SIZ];
14278
14279     if (strcmp(filename, "-") == 0) {
14280         return SavePosition(stdout, 0, NULL);
14281     } else {
14282         f = fopen(filename, "a");
14283         if (f == NULL) {
14284             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14285             DisplayError(buf, errno);
14286             return FALSE;
14287         } else {
14288             safeStrCpy(buf, lastMsg, MSG_SIZ);
14289             DisplayMessage(_("Waiting for access to save file"), "");
14290             flock(fileno(f), LOCK_EX); // [HGM] lock
14291             DisplayMessage(_("Saving position"), "");
14292             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14293             SavePosition(f, 0, NULL);
14294             DisplayMessage(buf, "");
14295             return TRUE;
14296         }
14297     }
14298 }
14299
14300 /* Save the current position to the given open file and close the file */
14301 int
14302 SavePosition (FILE *f, int dummy, char *dummy2)
14303 {
14304     time_t tm;
14305     char *fen;
14306
14307     if (gameMode == EditPosition) EditPositionDone(TRUE);
14308     if (appData.oldSaveStyle) {
14309         tm = time((time_t *) NULL);
14310
14311         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14312         PrintOpponents(f);
14313         fprintf(f, "[--------------\n");
14314         PrintPosition(f, currentMove);
14315         fprintf(f, "--------------]\n");
14316     } else {
14317         fen = PositionToFEN(currentMove, NULL, 1);
14318         fprintf(f, "%s\n", fen);
14319         free(fen);
14320     }
14321     fclose(f);
14322     return TRUE;
14323 }
14324
14325 void
14326 ReloadCmailMsgEvent (int unregister)
14327 {
14328 #if !WIN32
14329     static char *inFilename = NULL;
14330     static char *outFilename;
14331     int i;
14332     struct stat inbuf, outbuf;
14333     int status;
14334
14335     /* Any registered moves are unregistered if unregister is set, */
14336     /* i.e. invoked by the signal handler */
14337     if (unregister) {
14338         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14339             cmailMoveRegistered[i] = FALSE;
14340             if (cmailCommentList[i] != NULL) {
14341                 free(cmailCommentList[i]);
14342                 cmailCommentList[i] = NULL;
14343             }
14344         }
14345         nCmailMovesRegistered = 0;
14346     }
14347
14348     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14349         cmailResult[i] = CMAIL_NOT_RESULT;
14350     }
14351     nCmailResults = 0;
14352
14353     if (inFilename == NULL) {
14354         /* Because the filenames are static they only get malloced once  */
14355         /* and they never get freed                                      */
14356         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14357         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14358
14359         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14360         sprintf(outFilename, "%s.out", appData.cmailGameName);
14361     }
14362
14363     status = stat(outFilename, &outbuf);
14364     if (status < 0) {
14365         cmailMailedMove = FALSE;
14366     } else {
14367         status = stat(inFilename, &inbuf);
14368         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14369     }
14370
14371     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14372        counts the games, notes how each one terminated, etc.
14373
14374        It would be nice to remove this kludge and instead gather all
14375        the information while building the game list.  (And to keep it
14376        in the game list nodes instead of having a bunch of fixed-size
14377        parallel arrays.)  Note this will require getting each game's
14378        termination from the PGN tags, as the game list builder does
14379        not process the game moves.  --mann
14380        */
14381     cmailMsgLoaded = TRUE;
14382     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14383
14384     /* Load first game in the file or popup game menu */
14385     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14386
14387 #endif /* !WIN32 */
14388     return;
14389 }
14390
14391 int
14392 RegisterMove ()
14393 {
14394     FILE *f;
14395     char string[MSG_SIZ];
14396
14397     if (   cmailMailedMove
14398         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14399         return TRUE;            /* Allow free viewing  */
14400     }
14401
14402     /* Unregister move to ensure that we don't leave RegisterMove        */
14403     /* with the move registered when the conditions for registering no   */
14404     /* longer hold                                                       */
14405     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14406         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14407         nCmailMovesRegistered --;
14408
14409         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14410           {
14411               free(cmailCommentList[lastLoadGameNumber - 1]);
14412               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14413           }
14414     }
14415
14416     if (cmailOldMove == -1) {
14417         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14418         return FALSE;
14419     }
14420
14421     if (currentMove > cmailOldMove + 1) {
14422         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14423         return FALSE;
14424     }
14425
14426     if (currentMove < cmailOldMove) {
14427         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14428         return FALSE;
14429     }
14430
14431     if (forwardMostMove > currentMove) {
14432         /* Silently truncate extra moves */
14433         TruncateGame();
14434     }
14435
14436     if (   (currentMove == cmailOldMove + 1)
14437         || (   (currentMove == cmailOldMove)
14438             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14439                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14440         if (gameInfo.result != GameUnfinished) {
14441             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14442         }
14443
14444         if (commentList[currentMove] != NULL) {
14445             cmailCommentList[lastLoadGameNumber - 1]
14446               = StrSave(commentList[currentMove]);
14447         }
14448         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14449
14450         if (appData.debugMode)
14451           fprintf(debugFP, "Saving %s for game %d\n",
14452                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14453
14454         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14455
14456         f = fopen(string, "w");
14457         if (appData.oldSaveStyle) {
14458             SaveGameOldStyle(f); /* also closes the file */
14459
14460             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14461             f = fopen(string, "w");
14462             SavePosition(f, 0, NULL); /* also closes the file */
14463         } else {
14464             fprintf(f, "{--------------\n");
14465             PrintPosition(f, currentMove);
14466             fprintf(f, "--------------}\n\n");
14467
14468             SaveGame(f, 0, NULL); /* also closes the file*/
14469         }
14470
14471         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14472         nCmailMovesRegistered ++;
14473     } else if (nCmailGames == 1) {
14474         DisplayError(_("You have not made a move yet"), 0);
14475         return FALSE;
14476     }
14477
14478     return TRUE;
14479 }
14480
14481 void
14482 MailMoveEvent ()
14483 {
14484 #if !WIN32
14485     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14486     FILE *commandOutput;
14487     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14488     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14489     int nBuffers;
14490     int i;
14491     int archived;
14492     char *arcDir;
14493
14494     if (! cmailMsgLoaded) {
14495         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14496         return;
14497     }
14498
14499     if (nCmailGames == nCmailResults) {
14500         DisplayError(_("No unfinished games"), 0);
14501         return;
14502     }
14503
14504 #if CMAIL_PROHIBIT_REMAIL
14505     if (cmailMailedMove) {
14506       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);
14507         DisplayError(msg, 0);
14508         return;
14509     }
14510 #endif
14511
14512     if (! (cmailMailedMove || RegisterMove())) return;
14513
14514     if (   cmailMailedMove
14515         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14516       snprintf(string, MSG_SIZ, partCommandString,
14517                appData.debugMode ? " -v" : "", appData.cmailGameName);
14518         commandOutput = popen(string, "r");
14519
14520         if (commandOutput == NULL) {
14521             DisplayError(_("Failed to invoke cmail"), 0);
14522         } else {
14523             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14524                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14525             }
14526             if (nBuffers > 1) {
14527                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14528                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14529                 nBytes = MSG_SIZ - 1;
14530             } else {
14531                 (void) memcpy(msg, buffer, nBytes);
14532             }
14533             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14534
14535             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14536                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14537
14538                 archived = TRUE;
14539                 for (i = 0; i < nCmailGames; i ++) {
14540                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14541                         archived = FALSE;
14542                     }
14543                 }
14544                 if (   archived
14545                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14546                         != NULL)) {
14547                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14548                            arcDir,
14549                            appData.cmailGameName,
14550                            gameInfo.date);
14551                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14552                     cmailMsgLoaded = FALSE;
14553                 }
14554             }
14555
14556             DisplayInformation(msg);
14557             pclose(commandOutput);
14558         }
14559     } else {
14560         if ((*cmailMsg) != '\0') {
14561             DisplayInformation(cmailMsg);
14562         }
14563     }
14564
14565     return;
14566 #endif /* !WIN32 */
14567 }
14568
14569 char *
14570 CmailMsg ()
14571 {
14572 #if WIN32
14573     return NULL;
14574 #else
14575     int  prependComma = 0;
14576     char number[5];
14577     char string[MSG_SIZ];       /* Space for game-list */
14578     int  i;
14579
14580     if (!cmailMsgLoaded) return "";
14581
14582     if (cmailMailedMove) {
14583       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14584     } else {
14585         /* Create a list of games left */
14586       snprintf(string, MSG_SIZ, "[");
14587         for (i = 0; i < nCmailGames; i ++) {
14588             if (! (   cmailMoveRegistered[i]
14589                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14590                 if (prependComma) {
14591                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14592                 } else {
14593                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14594                     prependComma = 1;
14595                 }
14596
14597                 strcat(string, number);
14598             }
14599         }
14600         strcat(string, "]");
14601
14602         if (nCmailMovesRegistered + nCmailResults == 0) {
14603             switch (nCmailGames) {
14604               case 1:
14605                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14606                 break;
14607
14608               case 2:
14609                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14610                 break;
14611
14612               default:
14613                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14614                          nCmailGames);
14615                 break;
14616             }
14617         } else {
14618             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14619               case 1:
14620                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14621                          string);
14622                 break;
14623
14624               case 0:
14625                 if (nCmailResults == nCmailGames) {
14626                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14627                 } else {
14628                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14629                 }
14630                 break;
14631
14632               default:
14633                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14634                          string);
14635             }
14636         }
14637     }
14638     return cmailMsg;
14639 #endif /* WIN32 */
14640 }
14641
14642 void
14643 ResetGameEvent ()
14644 {
14645     if (gameMode == Training)
14646       SetTrainingModeOff();
14647
14648     Reset(TRUE, TRUE);
14649     cmailMsgLoaded = FALSE;
14650     if (appData.icsActive) {
14651       SendToICS(ics_prefix);
14652       SendToICS("refresh\n");
14653     }
14654 }
14655
14656 void
14657 ExitEvent (int status)
14658 {
14659     exiting++;
14660     if (exiting > 2) {
14661       /* Give up on clean exit */
14662       exit(status);
14663     }
14664     if (exiting > 1) {
14665       /* Keep trying for clean exit */
14666       return;
14667     }
14668
14669     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14670     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14671
14672     if (telnetISR != NULL) {
14673       RemoveInputSource(telnetISR);
14674     }
14675     if (icsPR != NoProc) {
14676       DestroyChildProcess(icsPR, TRUE);
14677     }
14678
14679     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14680     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14681
14682     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14683     /* make sure this other one finishes before killing it!                  */
14684     if(endingGame) { int count = 0;
14685         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14686         while(endingGame && count++ < 10) DoSleep(1);
14687         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14688     }
14689
14690     /* Kill off chess programs */
14691     if (first.pr != NoProc) {
14692         ExitAnalyzeMode();
14693
14694         DoSleep( appData.delayBeforeQuit );
14695         SendToProgram("quit\n", &first);
14696         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14697     }
14698     if (second.pr != NoProc) {
14699         DoSleep( appData.delayBeforeQuit );
14700         SendToProgram("quit\n", &second);
14701         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14702     }
14703     if (first.isr != NULL) {
14704         RemoveInputSource(first.isr);
14705     }
14706     if (second.isr != NULL) {
14707         RemoveInputSource(second.isr);
14708     }
14709
14710     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14711     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14712
14713     ShutDownFrontEnd();
14714     exit(status);
14715 }
14716
14717 void
14718 PauseEngine (ChessProgramState *cps)
14719 {
14720     SendToProgram("pause\n", cps);
14721     cps->pause = 2;
14722 }
14723
14724 void
14725 UnPauseEngine (ChessProgramState *cps)
14726 {
14727     SendToProgram("resume\n", cps);
14728     cps->pause = 1;
14729 }
14730
14731 void
14732 PauseEvent ()
14733 {
14734     if (appData.debugMode)
14735         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14736     if (pausing) {
14737         pausing = FALSE;
14738         ModeHighlight();
14739         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14740             StartClocks();
14741             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14742                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14743                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14744             }
14745             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14746             HandleMachineMove(stashedInputMove, stalledEngine);
14747             stalledEngine = NULL;
14748             return;
14749         }
14750         if (gameMode == MachinePlaysWhite ||
14751             gameMode == TwoMachinesPlay   ||
14752             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14753             if(first.pause)  UnPauseEngine(&first);
14754             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14755             if(second.pause) UnPauseEngine(&second);
14756             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14757             StartClocks();
14758         } else {
14759             DisplayBothClocks();
14760         }
14761         if (gameMode == PlayFromGameFile) {
14762             if (appData.timeDelay >= 0)
14763                 AutoPlayGameLoop();
14764         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14765             Reset(FALSE, TRUE);
14766             SendToICS(ics_prefix);
14767             SendToICS("refresh\n");
14768         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14769             ForwardInner(forwardMostMove);
14770         }
14771         pauseExamInvalid = FALSE;
14772     } else {
14773         switch (gameMode) {
14774           default:
14775             return;
14776           case IcsExamining:
14777             pauseExamForwardMostMove = forwardMostMove;
14778             pauseExamInvalid = FALSE;
14779             /* fall through */
14780           case IcsObserving:
14781           case IcsPlayingWhite:
14782           case IcsPlayingBlack:
14783             pausing = TRUE;
14784             ModeHighlight();
14785             return;
14786           case PlayFromGameFile:
14787             (void) StopLoadGameTimer();
14788             pausing = TRUE;
14789             ModeHighlight();
14790             break;
14791           case BeginningOfGame:
14792             if (appData.icsActive) return;
14793             /* else fall through */
14794           case MachinePlaysWhite:
14795           case MachinePlaysBlack:
14796           case TwoMachinesPlay:
14797             if (forwardMostMove == 0)
14798               return;           /* don't pause if no one has moved */
14799             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14800                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14801                 if(onMove->pause) {           // thinking engine can be paused
14802                     PauseEngine(onMove);      // do it
14803                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14804                         PauseEngine(onMove->other);
14805                     else
14806                         SendToProgram("easy\n", onMove->other);
14807                     StopClocks();
14808                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14809             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14810                 if(first.pause) {
14811                     PauseEngine(&first);
14812                     StopClocks();
14813                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14814             } else { // human on move, pause pondering by either method
14815                 if(first.pause)
14816                     PauseEngine(&first);
14817                 else if(appData.ponderNextMove)
14818                     SendToProgram("easy\n", &first);
14819                 StopClocks();
14820             }
14821             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14822           case AnalyzeMode:
14823             pausing = TRUE;
14824             ModeHighlight();
14825             break;
14826         }
14827     }
14828 }
14829
14830 void
14831 EditCommentEvent ()
14832 {
14833     char title[MSG_SIZ];
14834
14835     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14836       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14837     } else {
14838       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14839                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14840                parseList[currentMove - 1]);
14841     }
14842
14843     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14844 }
14845
14846
14847 void
14848 EditTagsEvent ()
14849 {
14850     char *tags = PGNTags(&gameInfo);
14851     bookUp = FALSE;
14852     EditTagsPopUp(tags, NULL);
14853     free(tags);
14854 }
14855
14856 void
14857 StartSecond ()
14858 {
14859     if(WaitForEngine(&second, StartSecond)) return;
14860     InitChessProgram(&second, FALSE);
14861     FeedMovesToProgram(&second, currentMove);
14862
14863     SendToProgram("analyze\n", &second);
14864     second.analyzing = TRUE;
14865     ThawUI();
14866 }
14867
14868 void
14869 ToggleSecond ()
14870 {
14871   if(second.analyzing) {
14872     SendToProgram("exit\n", &second);
14873     second.analyzing = FALSE;
14874   } else {
14875     StartSecond();
14876   }
14877 }
14878
14879 /* Toggle ShowThinking */
14880 void
14881 ToggleShowThinking()
14882 {
14883   appData.showThinking = !appData.showThinking;
14884   ShowThinkingEvent();
14885 }
14886
14887 int
14888 AnalyzeModeEvent ()
14889 {
14890     char buf[MSG_SIZ];
14891
14892     if (!first.analysisSupport) {
14893       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14894       DisplayError(buf, 0);
14895       return 0;
14896     }
14897     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14898     if (appData.icsActive) {
14899         if (gameMode != IcsObserving) {
14900           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14901             DisplayError(buf, 0);
14902             /* secure check */
14903             if (appData.icsEngineAnalyze) {
14904                 if (appData.debugMode)
14905                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14906                 ExitAnalyzeMode();
14907                 ModeHighlight();
14908             }
14909             return 0;
14910         }
14911         /* if enable, user wants to disable icsEngineAnalyze */
14912         if (appData.icsEngineAnalyze) {
14913                 ExitAnalyzeMode();
14914                 ModeHighlight();
14915                 return 0;
14916         }
14917         appData.icsEngineAnalyze = TRUE;
14918         if (appData.debugMode)
14919             fprintf(debugFP, "ICS engine analyze starting... \n");
14920     }
14921
14922     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14923     if (appData.noChessProgram || gameMode == AnalyzeMode)
14924       return 0;
14925
14926     if (gameMode != AnalyzeFile) {
14927         if (!appData.icsEngineAnalyze) {
14928                EditGameEvent();
14929                if (gameMode != EditGame) return 0;
14930         }
14931         if (!appData.showThinking) ToggleShowThinking();
14932         ResurrectChessProgram();
14933         SendToProgram("analyze\n", &first);
14934         first.analyzing = TRUE;
14935         /*first.maybeThinking = TRUE;*/
14936         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14937         EngineOutputPopUp();
14938     }
14939     if (!appData.icsEngineAnalyze) {
14940         gameMode = AnalyzeMode;
14941         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14942     }
14943     pausing = FALSE;
14944     ModeHighlight();
14945     SetGameInfo();
14946
14947     StartAnalysisClock();
14948     GetTimeMark(&lastNodeCountTime);
14949     lastNodeCount = 0;
14950     return 1;
14951 }
14952
14953 void
14954 AnalyzeFileEvent ()
14955 {
14956     if (appData.noChessProgram || gameMode == AnalyzeFile)
14957       return;
14958
14959     if (!first.analysisSupport) {
14960       char buf[MSG_SIZ];
14961       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14962       DisplayError(buf, 0);
14963       return;
14964     }
14965
14966     if (gameMode != AnalyzeMode) {
14967         keepInfo = 1; // mere annotating should not alter PGN tags
14968         EditGameEvent();
14969         keepInfo = 0;
14970         if (gameMode != EditGame) return;
14971         if (!appData.showThinking) ToggleShowThinking();
14972         ResurrectChessProgram();
14973         SendToProgram("analyze\n", &first);
14974         first.analyzing = TRUE;
14975         /*first.maybeThinking = TRUE;*/
14976         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14977         EngineOutputPopUp();
14978     }
14979     gameMode = AnalyzeFile;
14980     pausing = FALSE;
14981     ModeHighlight();
14982
14983     StartAnalysisClock();
14984     GetTimeMark(&lastNodeCountTime);
14985     lastNodeCount = 0;
14986     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14987     AnalysisPeriodicEvent(1);
14988 }
14989
14990 void
14991 MachineWhiteEvent ()
14992 {
14993     char buf[MSG_SIZ];
14994     char *bookHit = NULL;
14995
14996     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14997       return;
14998
14999
15000     if (gameMode == PlayFromGameFile ||
15001         gameMode == TwoMachinesPlay  ||
15002         gameMode == Training         ||
15003         gameMode == AnalyzeMode      ||
15004         gameMode == EndOfGame)
15005         EditGameEvent();
15006
15007     if (gameMode == EditPosition)
15008         EditPositionDone(TRUE);
15009
15010     if (!WhiteOnMove(currentMove)) {
15011         DisplayError(_("It is not White's turn"), 0);
15012         return;
15013     }
15014
15015     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15016       ExitAnalyzeMode();
15017
15018     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15019         gameMode == AnalyzeFile)
15020         TruncateGame();
15021
15022     ResurrectChessProgram();    /* in case it isn't running */
15023     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15024         gameMode = MachinePlaysWhite;
15025         ResetClocks();
15026     } else
15027     gameMode = MachinePlaysWhite;
15028     pausing = FALSE;
15029     ModeHighlight();
15030     SetGameInfo();
15031     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15032     DisplayTitle(buf);
15033     if (first.sendName) {
15034       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15035       SendToProgram(buf, &first);
15036     }
15037     if (first.sendTime) {
15038       if (first.useColors) {
15039         SendToProgram("black\n", &first); /*gnu kludge*/
15040       }
15041       SendTimeRemaining(&first, TRUE);
15042     }
15043     if (first.useColors) {
15044       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15045     }
15046     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15047     SetMachineThinkingEnables();
15048     first.maybeThinking = TRUE;
15049     StartClocks();
15050     firstMove = FALSE;
15051
15052     if (appData.autoFlipView && !flipView) {
15053       flipView = !flipView;
15054       DrawPosition(FALSE, NULL);
15055       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15056     }
15057
15058     if(bookHit) { // [HGM] book: simulate book reply
15059         static char bookMove[MSG_SIZ]; // a bit generous?
15060
15061         programStats.nodes = programStats.depth = programStats.time =
15062         programStats.score = programStats.got_only_move = 0;
15063         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15064
15065         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15066         strcat(bookMove, bookHit);
15067         savedMessage = bookMove; // args for deferred call
15068         savedState = &first;
15069         ScheduleDelayedEvent(DeferredBookMove, 1);
15070     }
15071 }
15072
15073 void
15074 MachineBlackEvent ()
15075 {
15076   char buf[MSG_SIZ];
15077   char *bookHit = NULL;
15078
15079     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15080         return;
15081
15082
15083     if (gameMode == PlayFromGameFile ||
15084         gameMode == TwoMachinesPlay  ||
15085         gameMode == Training         ||
15086         gameMode == AnalyzeMode      ||
15087         gameMode == EndOfGame)
15088         EditGameEvent();
15089
15090     if (gameMode == EditPosition)
15091         EditPositionDone(TRUE);
15092
15093     if (WhiteOnMove(currentMove)) {
15094         DisplayError(_("It is not Black's turn"), 0);
15095         return;
15096     }
15097
15098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15099       ExitAnalyzeMode();
15100
15101     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15102         gameMode == AnalyzeFile)
15103         TruncateGame();
15104
15105     ResurrectChessProgram();    /* in case it isn't running */
15106     gameMode = MachinePlaysBlack;
15107     pausing = FALSE;
15108     ModeHighlight();
15109     SetGameInfo();
15110     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15111     DisplayTitle(buf);
15112     if (first.sendName) {
15113       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15114       SendToProgram(buf, &first);
15115     }
15116     if (first.sendTime) {
15117       if (first.useColors) {
15118         SendToProgram("white\n", &first); /*gnu kludge*/
15119       }
15120       SendTimeRemaining(&first, FALSE);
15121     }
15122     if (first.useColors) {
15123       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15124     }
15125     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15126     SetMachineThinkingEnables();
15127     first.maybeThinking = TRUE;
15128     StartClocks();
15129
15130     if (appData.autoFlipView && flipView) {
15131       flipView = !flipView;
15132       DrawPosition(FALSE, NULL);
15133       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15134     }
15135     if(bookHit) { // [HGM] book: simulate book reply
15136         static char bookMove[MSG_SIZ]; // a bit generous?
15137
15138         programStats.nodes = programStats.depth = programStats.time =
15139         programStats.score = programStats.got_only_move = 0;
15140         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15141
15142         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15143         strcat(bookMove, bookHit);
15144         savedMessage = bookMove; // args for deferred call
15145         savedState = &first;
15146         ScheduleDelayedEvent(DeferredBookMove, 1);
15147     }
15148 }
15149
15150
15151 void
15152 DisplayTwoMachinesTitle ()
15153 {
15154     char buf[MSG_SIZ];
15155     if (appData.matchGames > 0) {
15156         if(appData.tourneyFile[0]) {
15157           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15158                    gameInfo.white, _("vs."), gameInfo.black,
15159                    nextGame+1, appData.matchGames+1,
15160                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15161         } else
15162         if (first.twoMachinesColor[0] == 'w') {
15163           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15164                    gameInfo.white, _("vs."),  gameInfo.black,
15165                    first.matchWins, second.matchWins,
15166                    matchGame - 1 - (first.matchWins + second.matchWins));
15167         } else {
15168           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15169                    gameInfo.white, _("vs."), gameInfo.black,
15170                    second.matchWins, first.matchWins,
15171                    matchGame - 1 - (first.matchWins + second.matchWins));
15172         }
15173     } else {
15174       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15175     }
15176     DisplayTitle(buf);
15177 }
15178
15179 void
15180 SettingsMenuIfReady ()
15181 {
15182   if (second.lastPing != second.lastPong) {
15183     DisplayMessage("", _("Waiting for second chess program"));
15184     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15185     return;
15186   }
15187   ThawUI();
15188   DisplayMessage("", "");
15189   SettingsPopUp(&second);
15190 }
15191
15192 int
15193 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15194 {
15195     char buf[MSG_SIZ];
15196     if (cps->pr == NoProc) {
15197         StartChessProgram(cps);
15198         if (cps->protocolVersion == 1) {
15199           retry();
15200           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15201         } else {
15202           /* kludge: allow timeout for initial "feature" command */
15203           if(retry != TwoMachinesEventIfReady) FreezeUI();
15204           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15205           DisplayMessage("", buf);
15206           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15207         }
15208         return 1;
15209     }
15210     return 0;
15211 }
15212
15213 void
15214 TwoMachinesEvent P((void))
15215 {
15216     int i, move = forwardMostMove;
15217     char buf[MSG_SIZ];
15218     ChessProgramState *onmove;
15219     char *bookHit = NULL;
15220     static int stalling = 0;
15221     TimeMark now;
15222     long wait;
15223
15224     if (appData.noChessProgram) return;
15225
15226     switch (gameMode) {
15227       case TwoMachinesPlay:
15228         return;
15229       case MachinePlaysWhite:
15230       case MachinePlaysBlack:
15231         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15232             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15233             return;
15234         }
15235         /* fall through */
15236       case BeginningOfGame:
15237       case PlayFromGameFile:
15238       case EndOfGame:
15239         EditGameEvent();
15240         if (gameMode != EditGame) return;
15241         break;
15242       case EditPosition:
15243         EditPositionDone(TRUE);
15244         break;
15245       case AnalyzeMode:
15246       case AnalyzeFile:
15247         ExitAnalyzeMode();
15248         break;
15249       case EditGame:
15250       default:
15251         break;
15252     }
15253
15254 //    forwardMostMove = currentMove;
15255     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15256     startingEngine = TRUE;
15257
15258     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15259
15260     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15261     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15262       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15263       return;
15264     }
15265   if(!appData.epd) {
15266     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15267
15268     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15269                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15270         startingEngine = matchMode = FALSE;
15271         DisplayError("second engine does not play this", 0);
15272         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15273         EditGameEvent(); // switch back to EditGame mode
15274         return;
15275     }
15276
15277     if(!stalling) {
15278       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15279       SendToProgram("force\n", &second);
15280       stalling = 1;
15281       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15282       return;
15283     }
15284   }
15285     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15286     if(appData.matchPause>10000 || appData.matchPause<10)
15287                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15288     wait = SubtractTimeMarks(&now, &pauseStart);
15289     if(wait < appData.matchPause) {
15290         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15291         return;
15292     }
15293     // we are now committed to starting the game
15294     stalling = 0;
15295     DisplayMessage("", "");
15296   if(!appData.epd) {
15297     if (startedFromSetupPosition) {
15298         SendBoard(&second, backwardMostMove);
15299     if (appData.debugMode) {
15300         fprintf(debugFP, "Two Machines\n");
15301     }
15302     }
15303     for (i = backwardMostMove; i < forwardMostMove; i++) {
15304         SendMoveToProgram(i, &second);
15305     }
15306   }
15307
15308     gameMode = TwoMachinesPlay;
15309     pausing = startingEngine = FALSE;
15310     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15311     SetGameInfo();
15312     DisplayTwoMachinesTitle();
15313     firstMove = TRUE;
15314     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15315         onmove = &first;
15316     } else {
15317         onmove = &second;
15318     }
15319     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15320     SendToProgram(first.computerString, &first);
15321     if (first.sendName) {
15322       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15323       SendToProgram(buf, &first);
15324     }
15325   if(!appData.epd) {
15326     SendToProgram(second.computerString, &second);
15327     if (second.sendName) {
15328       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15329       SendToProgram(buf, &second);
15330     }
15331   }
15332
15333     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15334         ResetClocks();
15335         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15336         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15337     }
15338     if (onmove->sendTime) {
15339       if (onmove->useColors) {
15340         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15341       }
15342       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15343     }
15344     if (onmove->useColors) {
15345       SendToProgram(onmove->twoMachinesColor, onmove);
15346     }
15347     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15348 //    SendToProgram("go\n", onmove);
15349     onmove->maybeThinking = TRUE;
15350     SetMachineThinkingEnables();
15351
15352     StartClocks();
15353
15354     if(bookHit) { // [HGM] book: simulate book reply
15355         static char bookMove[MSG_SIZ]; // a bit generous?
15356
15357         programStats.nodes = programStats.depth = programStats.time =
15358         programStats.score = programStats.got_only_move = 0;
15359         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15360
15361         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15362         strcat(bookMove, bookHit);
15363         savedMessage = bookMove; // args for deferred call
15364         savedState = onmove;
15365         ScheduleDelayedEvent(DeferredBookMove, 1);
15366     }
15367 }
15368
15369 void
15370 TrainingEvent ()
15371 {
15372     if (gameMode == Training) {
15373       SetTrainingModeOff();
15374       gameMode = PlayFromGameFile;
15375       DisplayMessage("", _("Training mode off"));
15376     } else {
15377       gameMode = Training;
15378       animateTraining = appData.animate;
15379
15380       /* make sure we are not already at the end of the game */
15381       if (currentMove < forwardMostMove) {
15382         SetTrainingModeOn();
15383         DisplayMessage("", _("Training mode on"));
15384       } else {
15385         gameMode = PlayFromGameFile;
15386         DisplayError(_("Already at end of game"), 0);
15387       }
15388     }
15389     ModeHighlight();
15390 }
15391
15392 void
15393 IcsClientEvent ()
15394 {
15395     if (!appData.icsActive) return;
15396     switch (gameMode) {
15397       case IcsPlayingWhite:
15398       case IcsPlayingBlack:
15399       case IcsObserving:
15400       case IcsIdle:
15401       case BeginningOfGame:
15402       case IcsExamining:
15403         return;
15404
15405       case EditGame:
15406         break;
15407
15408       case EditPosition:
15409         EditPositionDone(TRUE);
15410         break;
15411
15412       case AnalyzeMode:
15413       case AnalyzeFile:
15414         ExitAnalyzeMode();
15415         break;
15416
15417       default:
15418         EditGameEvent();
15419         break;
15420     }
15421
15422     gameMode = IcsIdle;
15423     ModeHighlight();
15424     return;
15425 }
15426
15427 void
15428 EditGameEvent ()
15429 {
15430     int i;
15431
15432     switch (gameMode) {
15433       case Training:
15434         SetTrainingModeOff();
15435         break;
15436       case MachinePlaysWhite:
15437       case MachinePlaysBlack:
15438       case BeginningOfGame:
15439         SendToProgram("force\n", &first);
15440         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15441             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15442                 char buf[MSG_SIZ];
15443                 abortEngineThink = TRUE;
15444                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15445                 SendToProgram(buf, &first);
15446                 DisplayMessage("Aborting engine think", "");
15447                 FreezeUI();
15448             }
15449         }
15450         SetUserThinkingEnables();
15451         break;
15452       case PlayFromGameFile:
15453         (void) StopLoadGameTimer();
15454         if (gameFileFP != NULL) {
15455             gameFileFP = NULL;
15456         }
15457         break;
15458       case EditPosition:
15459         EditPositionDone(TRUE);
15460         break;
15461       case AnalyzeMode:
15462       case AnalyzeFile:
15463         ExitAnalyzeMode();
15464         SendToProgram("force\n", &first);
15465         break;
15466       case TwoMachinesPlay:
15467         GameEnds(EndOfFile, NULL, GE_PLAYER);
15468         ResurrectChessProgram();
15469         SetUserThinkingEnables();
15470         break;
15471       case EndOfGame:
15472         ResurrectChessProgram();
15473         break;
15474       case IcsPlayingBlack:
15475       case IcsPlayingWhite:
15476         DisplayError(_("Warning: You are still playing a game"), 0);
15477         break;
15478       case IcsObserving:
15479         DisplayError(_("Warning: You are still observing a game"), 0);
15480         break;
15481       case IcsExamining:
15482         DisplayError(_("Warning: You are still examining a game"), 0);
15483         break;
15484       case IcsIdle:
15485         break;
15486       case EditGame:
15487       default:
15488         return;
15489     }
15490
15491     pausing = FALSE;
15492     StopClocks();
15493     first.offeredDraw = second.offeredDraw = 0;
15494
15495     if (gameMode == PlayFromGameFile) {
15496         whiteTimeRemaining = timeRemaining[0][currentMove];
15497         blackTimeRemaining = timeRemaining[1][currentMove];
15498         DisplayTitle("");
15499     }
15500
15501     if (gameMode == MachinePlaysWhite ||
15502         gameMode == MachinePlaysBlack ||
15503         gameMode == TwoMachinesPlay ||
15504         gameMode == EndOfGame) {
15505         i = forwardMostMove;
15506         while (i > currentMove) {
15507             SendToProgram("undo\n", &first);
15508             i--;
15509         }
15510         if(!adjustedClock) {
15511         whiteTimeRemaining = timeRemaining[0][currentMove];
15512         blackTimeRemaining = timeRemaining[1][currentMove];
15513         DisplayBothClocks();
15514         }
15515         if (whiteFlag || blackFlag) {
15516             whiteFlag = blackFlag = 0;
15517         }
15518         DisplayTitle("");
15519     }
15520
15521     gameMode = EditGame;
15522     ModeHighlight();
15523     SetGameInfo();
15524 }
15525
15526 void
15527 EditPositionEvent ()
15528 {
15529     int i;
15530     if (gameMode == EditPosition) {
15531         EditGameEvent();
15532         return;
15533     }
15534
15535     EditGameEvent();
15536     if (gameMode != EditGame) return;
15537
15538     gameMode = EditPosition;
15539     ModeHighlight();
15540     SetGameInfo();
15541     CopyBoard(rightsBoard, nullBoard);
15542     if (currentMove > 0)
15543       CopyBoard(boards[0], boards[currentMove]);
15544     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15545       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15546
15547     blackPlaysFirst = !WhiteOnMove(currentMove);
15548     ResetClocks();
15549     currentMove = forwardMostMove = backwardMostMove = 0;
15550     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15551     DisplayMove(-1);
15552     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15553 }
15554
15555 void
15556 ExitAnalyzeMode ()
15557 {
15558     /* [DM] icsEngineAnalyze - possible call from other functions */
15559     if (appData.icsEngineAnalyze) {
15560         appData.icsEngineAnalyze = FALSE;
15561
15562         DisplayMessage("",_("Close ICS engine analyze..."));
15563     }
15564     if (first.analysisSupport && first.analyzing) {
15565       SendToBoth("exit\n");
15566       first.analyzing = second.analyzing = FALSE;
15567     }
15568     thinkOutput[0] = NULLCHAR;
15569 }
15570
15571 void
15572 EditPositionDone (Boolean fakeRights)
15573 {
15574     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15575
15576     startedFromSetupPosition = TRUE;
15577     InitChessProgram(&first, FALSE);
15578     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15579       int r, f;
15580       boards[0][EP_STATUS] = EP_NONE;
15581       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15582       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15583         if(rightsBoard[r][f]) {
15584           ChessSquare p = boards[0][r][f];
15585           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15586           else if(p == king) boards[0][CASTLING][2] = f;
15587           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15588           else rightsBoard[r][f] = 2; // mark for second pass
15589         }
15590       }
15591       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15592         if(rightsBoard[r][f] == 2) {
15593           ChessSquare p = boards[0][r][f];
15594           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15595           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15596         }
15597       }
15598     }
15599     SendToProgram("force\n", &first);
15600     if (blackPlaysFirst) {
15601         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15602         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15603         currentMove = forwardMostMove = backwardMostMove = 1;
15604         CopyBoard(boards[1], boards[0]);
15605     } else {
15606         currentMove = forwardMostMove = backwardMostMove = 0;
15607     }
15608     SendBoard(&first, forwardMostMove);
15609     if (appData.debugMode) {
15610         fprintf(debugFP, "EditPosDone\n");
15611     }
15612     DisplayTitle("");
15613     DisplayMessage("", "");
15614     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15615     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15616     gameMode = EditGame;
15617     ModeHighlight();
15618     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15619     ClearHighlights(); /* [AS] */
15620 }
15621
15622 /* Pause for `ms' milliseconds */
15623 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15624 void
15625 TimeDelay (long ms)
15626 {
15627     TimeMark m1, m2;
15628
15629     GetTimeMark(&m1);
15630     do {
15631         GetTimeMark(&m2);
15632     } while (SubtractTimeMarks(&m2, &m1) < ms);
15633 }
15634
15635 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15636 void
15637 SendMultiLineToICS (char *buf)
15638 {
15639     char temp[MSG_SIZ+1], *p;
15640     int len;
15641
15642     len = strlen(buf);
15643     if (len > MSG_SIZ)
15644       len = MSG_SIZ;
15645
15646     strncpy(temp, buf, len);
15647     temp[len] = 0;
15648
15649     p = temp;
15650     while (*p) {
15651         if (*p == '\n' || *p == '\r')
15652           *p = ' ';
15653         ++p;
15654     }
15655
15656     strcat(temp, "\n");
15657     SendToICS(temp);
15658     SendToPlayer(temp, strlen(temp));
15659 }
15660
15661 void
15662 SetWhiteToPlayEvent ()
15663 {
15664     if (gameMode == EditPosition) {
15665         blackPlaysFirst = FALSE;
15666         DisplayBothClocks();    /* works because currentMove is 0 */
15667     } else if (gameMode == IcsExamining) {
15668         SendToICS(ics_prefix);
15669         SendToICS("tomove white\n");
15670     }
15671 }
15672
15673 void
15674 SetBlackToPlayEvent ()
15675 {
15676     if (gameMode == EditPosition) {
15677         blackPlaysFirst = TRUE;
15678         currentMove = 1;        /* kludge */
15679         DisplayBothClocks();
15680         currentMove = 0;
15681     } else if (gameMode == IcsExamining) {
15682         SendToICS(ics_prefix);
15683         SendToICS("tomove black\n");
15684     }
15685 }
15686
15687 void
15688 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15689 {
15690     char buf[MSG_SIZ];
15691     ChessSquare piece = boards[0][y][x];
15692     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15693     static int lastVariant;
15694     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15695
15696     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15697
15698     switch (selection) {
15699       case ClearBoard:
15700         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15701         MarkTargetSquares(1);
15702         CopyBoard(currentBoard, boards[0]);
15703         CopyBoard(menuBoard, initialPosition);
15704         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15705             SendToICS(ics_prefix);
15706             SendToICS("bsetup clear\n");
15707         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15708             SendToICS(ics_prefix);
15709             SendToICS("clearboard\n");
15710         } else {
15711             int nonEmpty = 0;
15712             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15713                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15714                 for (y = 0; y < BOARD_HEIGHT; y++) {
15715                     if (gameMode == IcsExamining) {
15716                         if (boards[currentMove][y][x] != EmptySquare) {
15717                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15718                                     AAA + x, ONE + y);
15719                             SendToICS(buf);
15720                         }
15721                     } else if(boards[0][y][x] != DarkSquare) {
15722                         if(boards[0][y][x] != p) nonEmpty++;
15723                         boards[0][y][x] = p;
15724                     }
15725                 }
15726             }
15727             CopyBoard(rightsBoard, nullBoard);
15728             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15729                 int r, i;
15730                 for(r = 0; r < BOARD_HEIGHT; r++) {
15731                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15732                     ChessSquare p = menuBoard[r][x];
15733                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15734                   }
15735                 }
15736                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15737                 DisplayMessage("Clicking clock again restores position", "");
15738                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15739                 if(!nonEmpty) { // asked to clear an empty board
15740                     CopyBoard(boards[0], menuBoard);
15741                 } else
15742                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15743                     CopyBoard(boards[0], initialPosition);
15744                 } else
15745                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15746                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15747                     CopyBoard(boards[0], erasedBoard);
15748                 } else
15749                     CopyBoard(erasedBoard, currentBoard);
15750
15751                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15752                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15753             }
15754         }
15755         if (gameMode == EditPosition) {
15756             DrawPosition(FALSE, boards[0]);
15757         }
15758         break;
15759
15760       case WhitePlay:
15761         SetWhiteToPlayEvent();
15762         break;
15763
15764       case BlackPlay:
15765         SetBlackToPlayEvent();
15766         break;
15767
15768       case EmptySquare:
15769         if (gameMode == IcsExamining) {
15770             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15771             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15772             SendToICS(buf);
15773         } else {
15774             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15775                 if(x == BOARD_LEFT-2) {
15776                     if(y < handSize-1-gameInfo.holdingsSize) break;
15777                     boards[0][y][1] = 0;
15778                 } else
15779                 if(x == BOARD_RGHT+1) {
15780                     if(y >= gameInfo.holdingsSize) break;
15781                     boards[0][y][BOARD_WIDTH-2] = 0;
15782                 } else break;
15783             }
15784             boards[0][y][x] = EmptySquare;
15785             DrawPosition(FALSE, boards[0]);
15786         }
15787         break;
15788
15789       case PromotePiece:
15790         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15791            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15792             selection = (ChessSquare) (PROMOTED(piece));
15793         } else if(piece == EmptySquare) selection = WhiteSilver;
15794         else selection = (ChessSquare)((int)piece - 1);
15795         goto defaultlabel;
15796
15797       case DemotePiece:
15798         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15799            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15800             selection = (ChessSquare) (DEMOTED(piece));
15801         } else if(piece == EmptySquare) selection = BlackSilver;
15802         else selection = (ChessSquare)((int)piece + 1);
15803         goto defaultlabel;
15804
15805       case WhiteQueen:
15806       case BlackQueen:
15807         if(gameInfo.variant == VariantShatranj ||
15808            gameInfo.variant == VariantXiangqi  ||
15809            gameInfo.variant == VariantCourier  ||
15810            gameInfo.variant == VariantASEAN    ||
15811            gameInfo.variant == VariantMakruk     )
15812             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15813         goto defaultlabel;
15814
15815       case WhiteRook:
15816         baseRank = 0;
15817       case BlackRook:
15818         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15819         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15820         goto defaultlabel;
15821
15822       case WhiteKing:
15823         baseRank = 0;
15824       case BlackKing:
15825         if(gameInfo.variant == VariantXiangqi)
15826             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15827         if(gameInfo.variant == VariantKnightmate)
15828             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15829         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15830       default:
15831         defaultlabel:
15832         if (gameMode == IcsExamining) {
15833             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15834             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15835                      PieceToChar(selection), AAA + x, ONE + y);
15836             SendToICS(buf);
15837         } else {
15838             rightsBoard[y][x] = hasRights;
15839             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15840                 int n;
15841                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15842                     n = PieceToNumber(selection - BlackPawn);
15843                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15844                     boards[0][handSize-1-n][0] = selection;
15845                     boards[0][handSize-1-n][1]++;
15846                 } else
15847                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15848                     n = PieceToNumber(selection);
15849                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15850                     boards[0][n][BOARD_WIDTH-1] = selection;
15851                     boards[0][n][BOARD_WIDTH-2]++;
15852                 }
15853             } else
15854             boards[0][y][x] = selection;
15855             DrawPosition(TRUE, boards[0]);
15856             ClearHighlights();
15857             fromX = fromY = -1;
15858         }
15859         break;
15860     }
15861 }
15862
15863
15864 void
15865 DropMenuEvent (ChessSquare selection, int x, int y)
15866 {
15867     ChessMove moveType;
15868
15869     switch (gameMode) {
15870       case IcsPlayingWhite:
15871       case MachinePlaysBlack:
15872         if (!WhiteOnMove(currentMove)) {
15873             DisplayMoveError(_("It is Black's turn"));
15874             return;
15875         }
15876         moveType = WhiteDrop;
15877         break;
15878       case IcsPlayingBlack:
15879       case MachinePlaysWhite:
15880         if (WhiteOnMove(currentMove)) {
15881             DisplayMoveError(_("It is White's turn"));
15882             return;
15883         }
15884         moveType = BlackDrop;
15885         break;
15886       case EditGame:
15887         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15888         break;
15889       default:
15890         return;
15891     }
15892
15893     if (moveType == BlackDrop && selection < BlackPawn) {
15894       selection = (ChessSquare) ((int) selection
15895                                  + (int) BlackPawn - (int) WhitePawn);
15896     }
15897     if (boards[currentMove][y][x] != EmptySquare) {
15898         DisplayMoveError(_("That square is occupied"));
15899         return;
15900     }
15901
15902     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15903 }
15904
15905 void
15906 AcceptEvent ()
15907 {
15908     /* Accept a pending offer of any kind from opponent */
15909
15910     if (appData.icsActive) {
15911         SendToICS(ics_prefix);
15912         SendToICS("accept\n");
15913     } else if (cmailMsgLoaded) {
15914         if (currentMove == cmailOldMove &&
15915             commentList[cmailOldMove] != NULL &&
15916             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15917                    "Black offers a draw" : "White offers a draw")) {
15918             TruncateGame();
15919             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15920             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15921         } else {
15922             DisplayError(_("There is no pending offer on this move"), 0);
15923             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15924         }
15925     } else {
15926         /* Not used for offers from chess program */
15927     }
15928 }
15929
15930 void
15931 DeclineEvent ()
15932 {
15933     /* Decline a pending offer of any kind from opponent */
15934
15935     if (appData.icsActive) {
15936         SendToICS(ics_prefix);
15937         SendToICS("decline\n");
15938     } else if (cmailMsgLoaded) {
15939         if (currentMove == cmailOldMove &&
15940             commentList[cmailOldMove] != NULL &&
15941             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15942                    "Black offers a draw" : "White offers a draw")) {
15943 #ifdef NOTDEF
15944             AppendComment(cmailOldMove, "Draw declined", TRUE);
15945             DisplayComment(cmailOldMove - 1, "Draw declined");
15946 #endif /*NOTDEF*/
15947         } else {
15948             DisplayError(_("There is no pending offer on this move"), 0);
15949         }
15950     } else {
15951         /* Not used for offers from chess program */
15952     }
15953 }
15954
15955 void
15956 RematchEvent ()
15957 {
15958     /* Issue ICS rematch command */
15959     if (appData.icsActive) {
15960         SendToICS(ics_prefix);
15961         SendToICS("rematch\n");
15962     }
15963 }
15964
15965 void
15966 CallFlagEvent ()
15967 {
15968     /* Call your opponent's flag (claim a win on time) */
15969     if (appData.icsActive) {
15970         SendToICS(ics_prefix);
15971         SendToICS("flag\n");
15972     } else {
15973         switch (gameMode) {
15974           default:
15975             return;
15976           case MachinePlaysWhite:
15977             if (whiteFlag) {
15978                 if (blackFlag)
15979                   GameEnds(GameIsDrawn, "Both players ran out of time",
15980                            GE_PLAYER);
15981                 else
15982                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15983             } else {
15984                 DisplayError(_("Your opponent is not out of time"), 0);
15985             }
15986             break;
15987           case MachinePlaysBlack:
15988             if (blackFlag) {
15989                 if (whiteFlag)
15990                   GameEnds(GameIsDrawn, "Both players ran out of time",
15991                            GE_PLAYER);
15992                 else
15993                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15994             } else {
15995                 DisplayError(_("Your opponent is not out of time"), 0);
15996             }
15997             break;
15998         }
15999     }
16000 }
16001
16002 void
16003 ClockClick (int which)
16004 {       // [HGM] code moved to back-end from winboard.c
16005         if(which) { // black clock
16006           if (gameMode == EditPosition || gameMode == IcsExamining) {
16007             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16008             SetBlackToPlayEvent();
16009           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16010                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
16011           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
16012           } else if (shiftKey) {
16013             AdjustClock(which, -1);
16014           } else if (gameMode == IcsPlayingWhite ||
16015                      gameMode == MachinePlaysBlack) {
16016             CallFlagEvent();
16017           }
16018         } else { // white clock
16019           if (gameMode == EditPosition || gameMode == IcsExamining) {
16020             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16021             SetWhiteToPlayEvent();
16022           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16023                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16024           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16025           } else if (shiftKey) {
16026             AdjustClock(which, -1);
16027           } else if (gameMode == IcsPlayingBlack ||
16028                    gameMode == MachinePlaysWhite) {
16029             CallFlagEvent();
16030           }
16031         }
16032 }
16033
16034 void
16035 DrawEvent ()
16036 {
16037     /* Offer draw or accept pending draw offer from opponent */
16038
16039     if (appData.icsActive) {
16040         /* Note: tournament rules require draw offers to be
16041            made after you make your move but before you punch
16042            your clock.  Currently ICS doesn't let you do that;
16043            instead, you immediately punch your clock after making
16044            a move, but you can offer a draw at any time. */
16045
16046         SendToICS(ics_prefix);
16047         SendToICS("draw\n");
16048         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16049     } else if (cmailMsgLoaded) {
16050         if (currentMove == cmailOldMove &&
16051             commentList[cmailOldMove] != NULL &&
16052             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16053                    "Black offers a draw" : "White offers a draw")) {
16054             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16055             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16056         } else if (currentMove == cmailOldMove + 1) {
16057             char *offer = WhiteOnMove(cmailOldMove) ?
16058               "White offers a draw" : "Black offers a draw";
16059             AppendComment(currentMove, offer, TRUE);
16060             DisplayComment(currentMove - 1, offer);
16061             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16062         } else {
16063             DisplayError(_("You must make your move before offering a draw"), 0);
16064             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16065         }
16066     } else if (first.offeredDraw) {
16067         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16068     } else {
16069         if (first.sendDrawOffers) {
16070             SendToProgram("draw\n", &first);
16071             userOfferedDraw = TRUE;
16072         }
16073     }
16074 }
16075
16076 void
16077 AdjournEvent ()
16078 {
16079     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16080
16081     if (appData.icsActive) {
16082         SendToICS(ics_prefix);
16083         SendToICS("adjourn\n");
16084     } else {
16085         /* Currently GNU Chess doesn't offer or accept Adjourns */
16086     }
16087 }
16088
16089
16090 void
16091 AbortEvent ()
16092 {
16093     /* Offer Abort or accept pending Abort offer from opponent */
16094
16095     if (appData.icsActive) {
16096         SendToICS(ics_prefix);
16097         SendToICS("abort\n");
16098     } else {
16099         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16100     }
16101 }
16102
16103 void
16104 ResignEvent ()
16105 {
16106     /* Resign.  You can do this even if it's not your turn. */
16107
16108     if (appData.icsActive) {
16109         SendToICS(ics_prefix);
16110         SendToICS("resign\n");
16111     } else {
16112         switch (gameMode) {
16113           case MachinePlaysWhite:
16114             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16115             break;
16116           case MachinePlaysBlack:
16117             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16118             break;
16119           case EditGame:
16120             if (cmailMsgLoaded) {
16121                 TruncateGame();
16122                 if (WhiteOnMove(cmailOldMove)) {
16123                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16124                 } else {
16125                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16126                 }
16127                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16128             }
16129             break;
16130           default:
16131             break;
16132         }
16133     }
16134 }
16135
16136
16137 void
16138 StopObservingEvent ()
16139 {
16140     /* Stop observing current games */
16141     SendToICS(ics_prefix);
16142     SendToICS("unobserve\n");
16143 }
16144
16145 void
16146 StopExaminingEvent ()
16147 {
16148     /* Stop observing current game */
16149     SendToICS(ics_prefix);
16150     SendToICS("unexamine\n");
16151 }
16152
16153 void
16154 ForwardInner (int target)
16155 {
16156     int limit; int oldSeekGraphUp = seekGraphUp;
16157
16158     if (appData.debugMode)
16159         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16160                 target, currentMove, forwardMostMove);
16161
16162     if (gameMode == EditPosition)
16163       return;
16164
16165     seekGraphUp = FALSE;
16166     MarkTargetSquares(1);
16167     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16168
16169     if (gameMode == PlayFromGameFile && !pausing)
16170       PauseEvent();
16171
16172     if (gameMode == IcsExamining && pausing)
16173       limit = pauseExamForwardMostMove;
16174     else
16175       limit = forwardMostMove;
16176
16177     if (target > limit) target = limit;
16178
16179     if (target > 0 && moveList[target - 1][0]) {
16180         int fromX, fromY, toX, toY;
16181         toX = moveList[target - 1][2] - AAA;
16182         toY = moveList[target - 1][3] - ONE;
16183         if (moveList[target - 1][1] == '@') {
16184             if (appData.highlightLastMove) {
16185                 SetHighlights(-1, -1, toX, toY);
16186             }
16187         } else {
16188             fromX = moveList[target - 1][0] - AAA;
16189             fromY = moveList[target - 1][1] - ONE;
16190             if (target == currentMove + 1) {
16191                 if(moveList[target - 1][4] == ';') { // multi-leg
16192                     killX = moveList[target - 1][5] - AAA;
16193                     killY = moveList[target - 1][6] - ONE;
16194                 }
16195                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16196                 killX = killY = -1;
16197             }
16198             if (appData.highlightLastMove) {
16199                 SetHighlights(fromX, fromY, toX, toY);
16200             }
16201         }
16202     }
16203     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16204         gameMode == Training || gameMode == PlayFromGameFile ||
16205         gameMode == AnalyzeFile) {
16206         while (currentMove < target) {
16207             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16208             SendMoveToProgram(currentMove++, &first);
16209         }
16210     } else {
16211         currentMove = target;
16212     }
16213
16214     if (gameMode == EditGame || gameMode == EndOfGame) {
16215         whiteTimeRemaining = timeRemaining[0][currentMove];
16216         blackTimeRemaining = timeRemaining[1][currentMove];
16217     }
16218     DisplayBothClocks();
16219     DisplayMove(currentMove - 1);
16220     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16221     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16222     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16223         DisplayComment(currentMove - 1, commentList[currentMove]);
16224     }
16225     ClearMap(); // [HGM] exclude: invalidate map
16226 }
16227
16228
16229 void
16230 ForwardEvent ()
16231 {
16232     if (gameMode == IcsExamining && !pausing) {
16233         SendToICS(ics_prefix);
16234         SendToICS("forward\n");
16235     } else {
16236         ForwardInner(currentMove + 1);
16237     }
16238 }
16239
16240 void
16241 ToEndEvent ()
16242 {
16243     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16244         /* to optimze, we temporarily turn off analysis mode while we feed
16245          * the remaining moves to the engine. Otherwise we get analysis output
16246          * after each move.
16247          */
16248         if (first.analysisSupport) {
16249           SendToProgram("exit\nforce\n", &first);
16250           first.analyzing = FALSE;
16251         }
16252     }
16253
16254     if (gameMode == IcsExamining && !pausing) {
16255         SendToICS(ics_prefix);
16256         SendToICS("forward 999999\n");
16257     } else {
16258         ForwardInner(forwardMostMove);
16259     }
16260
16261     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16262         /* we have fed all the moves, so reactivate analysis mode */
16263         SendToProgram("analyze\n", &first);
16264         first.analyzing = TRUE;
16265         /*first.maybeThinking = TRUE;*/
16266         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16267     }
16268 }
16269
16270 void
16271 BackwardInner (int target)
16272 {
16273     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16274
16275     if (appData.debugMode)
16276         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16277                 target, currentMove, forwardMostMove);
16278
16279     if (gameMode == EditPosition) return;
16280     seekGraphUp = FALSE;
16281     MarkTargetSquares(1);
16282     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16283     if (currentMove <= backwardMostMove) {
16284         ClearHighlights();
16285         DrawPosition(full_redraw, boards[currentMove]);
16286         return;
16287     }
16288     if (gameMode == PlayFromGameFile && !pausing)
16289       PauseEvent();
16290
16291     if (moveList[target][0]) {
16292         int fromX, fromY, toX, toY;
16293         toX = moveList[target][2] - AAA;
16294         toY = moveList[target][3] - ONE;
16295         if (moveList[target][1] == '@') {
16296             if (appData.highlightLastMove) {
16297                 SetHighlights(-1, -1, toX, toY);
16298             }
16299         } else {
16300             fromX = moveList[target][0] - AAA;
16301             fromY = moveList[target][1] - ONE;
16302             if (target == currentMove - 1) {
16303                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16304             }
16305             if (appData.highlightLastMove) {
16306                 SetHighlights(fromX, fromY, toX, toY);
16307             }
16308         }
16309     }
16310     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16311         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16312         while (currentMove > target) {
16313             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16314                 // null move cannot be undone. Reload program with move history before it.
16315                 int i;
16316                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16317                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16318                 }
16319                 SendBoard(&first, i);
16320               if(second.analyzing) SendBoard(&second, i);
16321                 for(currentMove=i; currentMove<target; currentMove++) {
16322                     SendMoveToProgram(currentMove, &first);
16323                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16324                 }
16325                 break;
16326             }
16327             SendToBoth("undo\n");
16328             currentMove--;
16329         }
16330     } else {
16331         currentMove = target;
16332     }
16333
16334     if (gameMode == EditGame || gameMode == EndOfGame) {
16335         whiteTimeRemaining = timeRemaining[0][currentMove];
16336         blackTimeRemaining = timeRemaining[1][currentMove];
16337     }
16338     DisplayBothClocks();
16339     DisplayMove(currentMove - 1);
16340     DrawPosition(full_redraw, boards[currentMove]);
16341     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16342     // [HGM] PV info: routine tests if comment empty
16343     DisplayComment(currentMove - 1, commentList[currentMove]);
16344     ClearMap(); // [HGM] exclude: invalidate map
16345 }
16346
16347 void
16348 BackwardEvent ()
16349 {
16350     if (gameMode == IcsExamining && !pausing) {
16351         SendToICS(ics_prefix);
16352         SendToICS("backward\n");
16353     } else {
16354         BackwardInner(currentMove - 1);
16355     }
16356 }
16357
16358 void
16359 ToStartEvent ()
16360 {
16361     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16362         /* to optimize, we temporarily turn off analysis mode while we undo
16363          * all the moves. Otherwise we get analysis output after each undo.
16364          */
16365         if (first.analysisSupport) {
16366           SendToProgram("exit\nforce\n", &first);
16367           first.analyzing = FALSE;
16368         }
16369     }
16370
16371     if (gameMode == IcsExamining && !pausing) {
16372         SendToICS(ics_prefix);
16373         SendToICS("backward 999999\n");
16374     } else {
16375         BackwardInner(backwardMostMove);
16376     }
16377
16378     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16379         /* we have fed all the moves, so reactivate analysis mode */
16380         SendToProgram("analyze\n", &first);
16381         first.analyzing = TRUE;
16382         /*first.maybeThinking = TRUE;*/
16383         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16384     }
16385 }
16386
16387 void
16388 ToNrEvent (int to)
16389 {
16390   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16391   if (to >= forwardMostMove) to = forwardMostMove;
16392   if (to <= backwardMostMove) to = backwardMostMove;
16393   if (to < currentMove) {
16394     BackwardInner(to);
16395   } else {
16396     ForwardInner(to);
16397   }
16398 }
16399
16400 void
16401 RevertEvent (Boolean annotate)
16402 {
16403     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16404         return;
16405     }
16406     if (gameMode != IcsExamining) {
16407         DisplayError(_("You are not examining a game"), 0);
16408         return;
16409     }
16410     if (pausing) {
16411         DisplayError(_("You can't revert while pausing"), 0);
16412         return;
16413     }
16414     SendToICS(ics_prefix);
16415     SendToICS("revert\n");
16416 }
16417
16418 void
16419 RetractMoveEvent ()
16420 {
16421     switch (gameMode) {
16422       case MachinePlaysWhite:
16423       case MachinePlaysBlack:
16424         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16425             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16426             return;
16427         }
16428         if (forwardMostMove < 2) return;
16429         currentMove = forwardMostMove = forwardMostMove - 2;
16430         whiteTimeRemaining = timeRemaining[0][currentMove];
16431         blackTimeRemaining = timeRemaining[1][currentMove];
16432         DisplayBothClocks();
16433         DisplayMove(currentMove - 1);
16434         ClearHighlights();/*!! could figure this out*/
16435         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16436         SendToProgram("remove\n", &first);
16437         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16438         break;
16439
16440       case BeginningOfGame:
16441       default:
16442         break;
16443
16444       case IcsPlayingWhite:
16445       case IcsPlayingBlack:
16446         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16447             SendToICS(ics_prefix);
16448             SendToICS("takeback 2\n");
16449         } else {
16450             SendToICS(ics_prefix);
16451             SendToICS("takeback 1\n");
16452         }
16453         break;
16454     }
16455 }
16456
16457 void
16458 MoveNowEvent ()
16459 {
16460     ChessProgramState *cps;
16461
16462     switch (gameMode) {
16463       case MachinePlaysWhite:
16464         if (!WhiteOnMove(forwardMostMove)) {
16465             DisplayError(_("It is your turn"), 0);
16466             return;
16467         }
16468         cps = &first;
16469         break;
16470       case MachinePlaysBlack:
16471         if (WhiteOnMove(forwardMostMove)) {
16472             DisplayError(_("It is your turn"), 0);
16473             return;
16474         }
16475         cps = &first;
16476         break;
16477       case TwoMachinesPlay:
16478         if (WhiteOnMove(forwardMostMove) ==
16479             (first.twoMachinesColor[0] == 'w')) {
16480             cps = &first;
16481         } else {
16482             cps = &second;
16483         }
16484         break;
16485       case BeginningOfGame:
16486       default:
16487         return;
16488     }
16489     SendToProgram("?\n", cps);
16490 }
16491
16492 void
16493 TruncateGameEvent ()
16494 {
16495     EditGameEvent();
16496     if (gameMode != EditGame) return;
16497     TruncateGame();
16498 }
16499
16500 void
16501 TruncateGame ()
16502 {
16503     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16504     if (forwardMostMove > currentMove) {
16505         if (gameInfo.resultDetails != NULL) {
16506             free(gameInfo.resultDetails);
16507             gameInfo.resultDetails = NULL;
16508             gameInfo.result = GameUnfinished;
16509         }
16510         forwardMostMove = currentMove;
16511         HistorySet(parseList, backwardMostMove, forwardMostMove,
16512                    currentMove-1);
16513     }
16514 }
16515
16516 void
16517 HintEvent ()
16518 {
16519     if (appData.noChessProgram) return;
16520     switch (gameMode) {
16521       case MachinePlaysWhite:
16522         if (WhiteOnMove(forwardMostMove)) {
16523             DisplayError(_("Wait until your turn."), 0);
16524             return;
16525         }
16526         break;
16527       case BeginningOfGame:
16528       case MachinePlaysBlack:
16529         if (!WhiteOnMove(forwardMostMove)) {
16530             DisplayError(_("Wait until your turn."), 0);
16531             return;
16532         }
16533         break;
16534       default:
16535         DisplayError(_("No hint available"), 0);
16536         return;
16537     }
16538     SendToProgram("hint\n", &first);
16539     hintRequested = TRUE;
16540 }
16541
16542 int
16543 SaveSelected (FILE *g, int dummy, char *dummy2)
16544 {
16545     ListGame * lg = (ListGame *) gameList.head;
16546     int nItem, cnt=0;
16547     FILE *f;
16548
16549     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16550         DisplayError(_("Game list not loaded or empty"), 0);
16551         return 0;
16552     }
16553
16554     creatingBook = TRUE; // suppresses stuff during load game
16555
16556     /* Get list size */
16557     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16558         if(lg->position >= 0) { // selected?
16559             LoadGame(f, nItem, "", TRUE);
16560             SaveGamePGN2(g); // leaves g open
16561             cnt++; DoEvents();
16562         }
16563         lg = (ListGame *) lg->node.succ;
16564     }
16565
16566     fclose(g);
16567     creatingBook = FALSE;
16568
16569     return cnt;
16570 }
16571
16572 void
16573 CreateBookEvent ()
16574 {
16575     ListGame * lg = (ListGame *) gameList.head;
16576     FILE *f, *g;
16577     int nItem;
16578     static int secondTime = FALSE;
16579
16580     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16581         DisplayError(_("Game list not loaded or empty"), 0);
16582         return;
16583     }
16584
16585     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16586         fclose(g);
16587         secondTime++;
16588         DisplayNote(_("Book file exists! Try again for overwrite."));
16589         return;
16590     }
16591
16592     creatingBook = TRUE;
16593     secondTime = FALSE;
16594
16595     /* Get list size */
16596     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16597         if(lg->position >= 0) {
16598             LoadGame(f, nItem, "", TRUE);
16599             AddGameToBook(TRUE);
16600             DoEvents();
16601         }
16602         lg = (ListGame *) lg->node.succ;
16603     }
16604
16605     creatingBook = FALSE;
16606     FlushBook();
16607 }
16608
16609 void
16610 BookEvent ()
16611 {
16612     if (appData.noChessProgram) return;
16613     switch (gameMode) {
16614       case MachinePlaysWhite:
16615         if (WhiteOnMove(forwardMostMove)) {
16616             DisplayError(_("Wait until your turn."), 0);
16617             return;
16618         }
16619         break;
16620       case BeginningOfGame:
16621       case MachinePlaysBlack:
16622         if (!WhiteOnMove(forwardMostMove)) {
16623             DisplayError(_("Wait until your turn."), 0);
16624             return;
16625         }
16626         break;
16627       case EditPosition:
16628         EditPositionDone(TRUE);
16629         break;
16630       case TwoMachinesPlay:
16631         return;
16632       default:
16633         break;
16634     }
16635     SendToProgram("bk\n", &first);
16636     bookOutput[0] = NULLCHAR;
16637     bookRequested = TRUE;
16638 }
16639
16640 void
16641 AboutGameEvent ()
16642 {
16643     char *tags = PGNTags(&gameInfo);
16644     TagsPopUp(tags, CmailMsg());
16645     free(tags);
16646 }
16647
16648 /* end button procedures */
16649
16650 void
16651 PrintPosition (FILE *fp, int move)
16652 {
16653     int i, j;
16654
16655     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16656         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16657             char c = PieceToChar(boards[move][i][j]);
16658             fputc(c == '?' ? '.' : c, fp);
16659             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16660         }
16661     }
16662     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16663       fprintf(fp, "white to play\n");
16664     else
16665       fprintf(fp, "black to play\n");
16666 }
16667
16668 void
16669 PrintOpponents (FILE *fp)
16670 {
16671     if (gameInfo.white != NULL) {
16672         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16673     } else {
16674         fprintf(fp, "\n");
16675     }
16676 }
16677
16678 /* Find last component of program's own name, using some heuristics */
16679 void
16680 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16681 {
16682     char *p, *q, c;
16683     int local = (strcmp(host, "localhost") == 0);
16684     while (!local && (p = strchr(prog, ';')) != NULL) {
16685         p++;
16686         while (*p == ' ') p++;
16687         prog = p;
16688     }
16689     if (*prog == '"' || *prog == '\'') {
16690         q = strchr(prog + 1, *prog);
16691     } else {
16692         q = strchr(prog, ' ');
16693     }
16694     if (q == NULL) q = prog + strlen(prog);
16695     p = q;
16696     while (p >= prog && *p != '/' && *p != '\\') p--;
16697     p++;
16698     if(p == prog && *p == '"') p++;
16699     c = *q; *q = 0;
16700     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16701     memcpy(buf, p, q - p);
16702     buf[q - p] = NULLCHAR;
16703     if (!local) {
16704         strcat(buf, "@");
16705         strcat(buf, host);
16706     }
16707 }
16708
16709 char *
16710 TimeControlTagValue ()
16711 {
16712     char buf[MSG_SIZ];
16713     if (!appData.clockMode) {
16714       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16715     } else if (movesPerSession > 0) {
16716       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16717     } else if (timeIncrement == 0) {
16718       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16719     } else {
16720       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16721     }
16722     return StrSave(buf);
16723 }
16724
16725 void
16726 SetGameInfo ()
16727 {
16728     /* This routine is used only for certain modes */
16729     VariantClass v = gameInfo.variant;
16730     ChessMove r = GameUnfinished;
16731     char *p = NULL;
16732
16733     if(keepInfo) return;
16734
16735     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16736         r = gameInfo.result;
16737         p = gameInfo.resultDetails;
16738         gameInfo.resultDetails = NULL;
16739     }
16740     ClearGameInfo(&gameInfo);
16741     gameInfo.variant = v;
16742
16743     switch (gameMode) {
16744       case MachinePlaysWhite:
16745         gameInfo.event = StrSave( appData.pgnEventHeader );
16746         gameInfo.site = StrSave(HostName());
16747         gameInfo.date = PGNDate();
16748         gameInfo.round = StrSave("-");
16749         gameInfo.white = StrSave(first.tidy);
16750         gameInfo.black = StrSave(UserName());
16751         gameInfo.timeControl = TimeControlTagValue();
16752         break;
16753
16754       case MachinePlaysBlack:
16755         gameInfo.event = StrSave( appData.pgnEventHeader );
16756         gameInfo.site = StrSave(HostName());
16757         gameInfo.date = PGNDate();
16758         gameInfo.round = StrSave("-");
16759         gameInfo.white = StrSave(UserName());
16760         gameInfo.black = StrSave(first.tidy);
16761         gameInfo.timeControl = TimeControlTagValue();
16762         break;
16763
16764       case TwoMachinesPlay:
16765         gameInfo.event = StrSave( appData.pgnEventHeader );
16766         gameInfo.site = StrSave(HostName());
16767         gameInfo.date = PGNDate();
16768         if (roundNr > 0) {
16769             char buf[MSG_SIZ];
16770             snprintf(buf, MSG_SIZ, "%d", roundNr);
16771             gameInfo.round = StrSave(buf);
16772         } else {
16773             gameInfo.round = StrSave("-");
16774         }
16775         if (first.twoMachinesColor[0] == 'w') {
16776             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16777             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16778         } else {
16779             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16780             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16781         }
16782         gameInfo.timeControl = TimeControlTagValue();
16783         break;
16784
16785       case EditGame:
16786         gameInfo.event = StrSave("Edited game");
16787         gameInfo.site = StrSave(HostName());
16788         gameInfo.date = PGNDate();
16789         gameInfo.round = StrSave("-");
16790         gameInfo.white = StrSave("-");
16791         gameInfo.black = StrSave("-");
16792         gameInfo.result = r;
16793         gameInfo.resultDetails = p;
16794         break;
16795
16796       case EditPosition:
16797         gameInfo.event = StrSave("Edited position");
16798         gameInfo.site = StrSave(HostName());
16799         gameInfo.date = PGNDate();
16800         gameInfo.round = StrSave("-");
16801         gameInfo.white = StrSave("-");
16802         gameInfo.black = StrSave("-");
16803         break;
16804
16805       case IcsPlayingWhite:
16806       case IcsPlayingBlack:
16807       case IcsObserving:
16808       case IcsExamining:
16809         break;
16810
16811       case PlayFromGameFile:
16812         gameInfo.event = StrSave("Game from non-PGN file");
16813         gameInfo.site = StrSave(HostName());
16814         gameInfo.date = PGNDate();
16815         gameInfo.round = StrSave("-");
16816         gameInfo.white = StrSave("?");
16817         gameInfo.black = StrSave("?");
16818         break;
16819
16820       default:
16821         break;
16822     }
16823 }
16824
16825 void
16826 ReplaceComment (int index, char *text)
16827 {
16828     int len;
16829     char *p;
16830     float score;
16831
16832     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16833        pvInfoList[index-1].depth == len &&
16834        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16835        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16836     while (*text == '\n') text++;
16837     len = strlen(text);
16838     while (len > 0 && text[len - 1] == '\n') len--;
16839
16840     if (commentList[index] != NULL)
16841       free(commentList[index]);
16842
16843     if (len == 0) {
16844         commentList[index] = NULL;
16845         return;
16846     }
16847   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16848       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16849       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16850     commentList[index] = (char *) malloc(len + 2);
16851     strncpy(commentList[index], text, len);
16852     commentList[index][len] = '\n';
16853     commentList[index][len + 1] = NULLCHAR;
16854   } else {
16855     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16856     char *p;
16857     commentList[index] = (char *) malloc(len + 7);
16858     safeStrCpy(commentList[index], "{\n", 3);
16859     safeStrCpy(commentList[index]+2, text, len+1);
16860     commentList[index][len+2] = NULLCHAR;
16861     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16862     strcat(commentList[index], "\n}\n");
16863   }
16864 }
16865
16866 void
16867 CrushCRs (char *text)
16868 {
16869   char *p = text;
16870   char *q = text;
16871   char ch;
16872
16873   do {
16874     ch = *p++;
16875     if (ch == '\r') continue;
16876     *q++ = ch;
16877   } while (ch != '\0');
16878 }
16879
16880 void
16881 AppendComment (int index, char *text, Boolean addBraces)
16882 /* addBraces  tells if we should add {} */
16883 {
16884     int oldlen, len;
16885     char *old;
16886
16887 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16888     if(addBraces == 3) addBraces = 0; else // force appending literally
16889     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16890
16891     CrushCRs(text);
16892     while (*text == '\n') text++;
16893     len = strlen(text);
16894     while (len > 0 && text[len - 1] == '\n') len--;
16895     text[len] = NULLCHAR;
16896
16897     if (len == 0) return;
16898
16899     if (commentList[index] != NULL) {
16900       Boolean addClosingBrace = addBraces;
16901         old = commentList[index];
16902         oldlen = strlen(old);
16903         while(commentList[index][oldlen-1] ==  '\n')
16904           commentList[index][--oldlen] = NULLCHAR;
16905         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16906         safeStrCpy(commentList[index], old, oldlen + len + 6);
16907         free(old);
16908         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16909         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16910           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16911           while (*text == '\n') { text++; len--; }
16912           commentList[index][--oldlen] = NULLCHAR;
16913       }
16914         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16915         else          strcat(commentList[index], "\n");
16916         strcat(commentList[index], text);
16917         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16918         else          strcat(commentList[index], "\n");
16919     } else {
16920         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16921         if(addBraces)
16922           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16923         else commentList[index][0] = NULLCHAR;
16924         strcat(commentList[index], text);
16925         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16926         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16927     }
16928 }
16929
16930 static char *
16931 FindStr (char * text, char * sub_text)
16932 {
16933     char * result = strstr( text, sub_text );
16934
16935     if( result != NULL ) {
16936         result += strlen( sub_text );
16937     }
16938
16939     return result;
16940 }
16941
16942 /* [AS] Try to extract PV info from PGN comment */
16943 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16944 char *
16945 GetInfoFromComment (int index, char * text)
16946 {
16947     char * sep = text, *p;
16948
16949     if( text != NULL && index > 0 ) {
16950         int score = 0;
16951         int depth = 0;
16952         int time = -1, sec = 0, deci;
16953         char * s_eval = FindStr( text, "[%eval " );
16954         char * s_emt = FindStr( text, "[%emt " );
16955 #if 0
16956         if( s_eval != NULL || s_emt != NULL ) {
16957 #else
16958         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16959 #endif
16960             /* New style */
16961             char delim;
16962
16963             if( s_eval != NULL ) {
16964                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16965                     return text;
16966                 }
16967
16968                 if( delim != ']' ) {
16969                     return text;
16970                 }
16971             }
16972
16973             if( s_emt != NULL ) {
16974             }
16975                 return text;
16976         }
16977         else {
16978             /* We expect something like: [+|-]nnn.nn/dd */
16979             int score_lo = 0;
16980
16981             if(*text != '{') return text; // [HGM] braces: must be normal comment
16982
16983             sep = strchr( text, '/' );
16984             if( sep == NULL || sep < (text+4) ) {
16985                 return text;
16986             }
16987
16988             p = text;
16989             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16990             if(p[1] == '(') { // comment starts with PV
16991                p = strchr(p, ')'); // locate end of PV
16992                if(p == NULL || sep < p+5) return text;
16993                // at this point we have something like "{(.*) +0.23/6 ..."
16994                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16995                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16996                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16997             }
16998             time = -1; sec = -1; deci = -1;
16999             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
17000                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
17001                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
17002                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
17003                 return text;
17004             }
17005
17006             if( score_lo < 0 || score_lo >= 100 ) {
17007                 return text;
17008             }
17009
17010             if(sec >= 0) time = 600*time + 10*sec; else
17011             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
17012
17013             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17014
17015             /* [HGM] PV time: now locate end of PV info */
17016             while( *++sep >= '0' && *sep <= '9'); // strip depth
17017             if(time >= 0)
17018             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17019             if(sec >= 0)
17020             while( *++sep >= '0' && *sep <= '9'); // strip seconds
17021             if(deci >= 0)
17022             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17023             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17024         }
17025
17026         if( depth <= 0 ) {
17027             return text;
17028         }
17029
17030         if( time < 0 ) {
17031             time = -1;
17032         }
17033
17034         pvInfoList[index-1].depth = depth;
17035         pvInfoList[index-1].score = score;
17036         pvInfoList[index-1].time  = 10*time; // centi-sec
17037         if(*sep == '}') *sep = 0; else *--sep = '{';
17038         if(p != text) {
17039             while(*p++ = *sep++)
17040                                 ;
17041             sep = text;
17042         } // squeeze out space between PV and comment, and return both
17043     }
17044     return sep;
17045 }
17046
17047 void
17048 SendToProgram (char *message, ChessProgramState *cps)
17049 {
17050     int count, outCount, error;
17051     char buf[MSG_SIZ];
17052
17053     if (cps->pr == NoProc) return;
17054     Attention(cps);
17055
17056     if (appData.debugMode) {
17057         TimeMark now;
17058         GetTimeMark(&now);
17059         fprintf(debugFP, "%ld >%-6s: %s",
17060                 SubtractTimeMarks(&now, &programStartTime),
17061                 cps->which, message);
17062         if(serverFP)
17063             fprintf(serverFP, "%ld >%-6s: %s",
17064                 SubtractTimeMarks(&now, &programStartTime),
17065                 cps->which, message), fflush(serverFP);
17066     }
17067
17068     count = strlen(message);
17069     outCount = OutputToProcess(cps->pr, message, count, &error);
17070     if (outCount < count && !exiting
17071                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17072       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17073       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17074         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17075             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17076                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17077                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17078                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17079             } else {
17080                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17081                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17082                 gameInfo.result = res;
17083             }
17084             gameInfo.resultDetails = StrSave(buf);
17085         }
17086         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17087         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17088     }
17089 }
17090
17091 void
17092 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17093 {
17094     char *end_str;
17095     char buf[MSG_SIZ];
17096     ChessProgramState *cps = (ChessProgramState *)closure;
17097
17098     if (isr != cps->isr) return; /* Killed intentionally */
17099     if (count <= 0) {
17100         if (count == 0) {
17101             RemoveInputSource(cps->isr);
17102             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17103                     _(cps->which), cps->program);
17104             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17105             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17106                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17107                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17108                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17109                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17110                 } else {
17111                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17112                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17113                     gameInfo.result = res;
17114                 }
17115                 gameInfo.resultDetails = StrSave(buf);
17116             }
17117             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17118             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17119         } else {
17120             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17121                     _(cps->which), cps->program);
17122             RemoveInputSource(cps->isr);
17123
17124             /* [AS] Program is misbehaving badly... kill it */
17125             if( count == -2 ) {
17126                 DestroyChildProcess( cps->pr, 9 );
17127                 cps->pr = NoProc;
17128             }
17129
17130             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17131         }
17132         return;
17133     }
17134
17135     if ((end_str = strchr(message, '\r')) != NULL)
17136       *end_str = NULLCHAR;
17137     if ((end_str = strchr(message, '\n')) != NULL)
17138       *end_str = NULLCHAR;
17139
17140     if (appData.debugMode) {
17141         TimeMark now; int print = 1;
17142         char *quote = ""; char c; int i;
17143
17144         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17145                 char start = message[0];
17146                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17147                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17148                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17149                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17150                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17151                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17152                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17153                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17154                    sscanf(message, "hint: %c", &c)!=1 &&
17155                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17156                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17157                     print = (appData.engineComments >= 2);
17158                 }
17159                 message[0] = start; // restore original message
17160         }
17161         if(print) {
17162                 GetTimeMark(&now);
17163                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17164                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17165                         quote,
17166                         message);
17167                 if(serverFP)
17168                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17169                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17170                         quote,
17171                         message), fflush(serverFP);
17172         }
17173     }
17174
17175     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17176     if (appData.icsEngineAnalyze) {
17177         if (strstr(message, "whisper") != NULL ||
17178              strstr(message, "kibitz") != NULL ||
17179             strstr(message, "tellics") != NULL) return;
17180     }
17181
17182     HandleMachineMove(message, cps);
17183 }
17184
17185
17186 void
17187 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17188 {
17189     char buf[MSG_SIZ];
17190     int seconds;
17191
17192     if( timeControl_2 > 0 ) {
17193         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17194             tc = timeControl_2;
17195         }
17196     }
17197     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17198     inc /= cps->timeOdds;
17199     st  /= cps->timeOdds;
17200
17201     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17202
17203     if (st > 0) {
17204       /* Set exact time per move, normally using st command */
17205       if (cps->stKludge) {
17206         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17207         seconds = st % 60;
17208         if (seconds == 0) {
17209           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17210         } else {
17211           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17212         }
17213       } else {
17214         snprintf(buf, MSG_SIZ, "st %d\n", st);
17215       }
17216     } else {
17217       /* Set conventional or incremental time control, using level command */
17218       if (seconds == 0) {
17219         /* Note old gnuchess bug -- minutes:seconds used to not work.
17220            Fixed in later versions, but still avoid :seconds
17221            when seconds is 0. */
17222         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17223       } else {
17224         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17225                  seconds, inc/1000.);
17226       }
17227     }
17228     SendToProgram(buf, cps);
17229
17230     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17231     /* Orthogonally, limit search to given depth */
17232     if (sd > 0) {
17233       if (cps->sdKludge) {
17234         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17235       } else {
17236         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17237       }
17238       SendToProgram(buf, cps);
17239     }
17240
17241     if(cps->nps >= 0) { /* [HGM] nps */
17242         if(cps->supportsNPS == FALSE)
17243           cps->nps = -1; // don't use if engine explicitly says not supported!
17244         else {
17245           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17246           SendToProgram(buf, cps);
17247         }
17248     }
17249 }
17250
17251 ChessProgramState *
17252 WhitePlayer ()
17253 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17254 {
17255     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17256        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17257         return &second;
17258     return &first;
17259 }
17260
17261 void
17262 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17263 {
17264     char message[MSG_SIZ];
17265     long time, otime;
17266
17267     /* Note: this routine must be called when the clocks are stopped
17268        or when they have *just* been set or switched; otherwise
17269        it will be off by the time since the current tick started.
17270     */
17271     if (machineWhite) {
17272         time = whiteTimeRemaining / 10;
17273         otime = blackTimeRemaining / 10;
17274     } else {
17275         time = blackTimeRemaining / 10;
17276         otime = whiteTimeRemaining / 10;
17277     }
17278     /* [HGM] translate opponent's time by time-odds factor */
17279     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17280
17281     if (time <= 0) time = 1;
17282     if (otime <= 0) otime = 1;
17283
17284     snprintf(message, MSG_SIZ, "time %ld\n", time);
17285     SendToProgram(message, cps);
17286
17287     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17288     SendToProgram(message, cps);
17289 }
17290
17291 char *
17292 EngineDefinedVariant (ChessProgramState *cps, int n)
17293 {   // return name of n-th unknown variant that engine supports
17294     static char buf[MSG_SIZ];
17295     char *p, *s = cps->variants;
17296     if(!s) return NULL;
17297     do { // parse string from variants feature
17298       VariantClass v;
17299         p = strchr(s, ',');
17300         if(p) *p = NULLCHAR;
17301       v = StringToVariant(s);
17302       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17303         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17304             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17305                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17306                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17307                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17308             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17309         }
17310         if(p) *p++ = ',';
17311         if(n < 0) return buf;
17312     } while(s = p);
17313     return NULL;
17314 }
17315
17316 int
17317 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17318 {
17319   char buf[MSG_SIZ];
17320   int len = strlen(name);
17321   int val;
17322
17323   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17324     (*p) += len + 1;
17325     sscanf(*p, "%d", &val);
17326     *loc = (val != 0);
17327     while (**p && **p != ' ')
17328       (*p)++;
17329     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17330     SendToProgram(buf, cps);
17331     return TRUE;
17332   }
17333   return FALSE;
17334 }
17335
17336 int
17337 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17338 {
17339   char buf[MSG_SIZ];
17340   int len = strlen(name);
17341   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17342     (*p) += len + 1;
17343     sscanf(*p, "%d", loc);
17344     while (**p && **p != ' ') (*p)++;
17345     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17346     SendToProgram(buf, cps);
17347     return TRUE;
17348   }
17349   return FALSE;
17350 }
17351
17352 int
17353 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17354 {
17355   char buf[MSG_SIZ];
17356   int len = strlen(name);
17357   if (strncmp((*p), name, len) == 0
17358       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17359     (*p) += len + 2;
17360     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17361     FREE(*loc); *loc = malloc(len);
17362     strncpy(*loc, *p, len);
17363     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17364     while (**p && **p != '\"') (*p)++;
17365     if (**p == '\"') (*p)++;
17366     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17367     SendToProgram(buf, cps);
17368     return TRUE;
17369   }
17370   return FALSE;
17371 }
17372
17373 int
17374 ParseOption (Option *opt, ChessProgramState *cps)
17375 // [HGM] options: process the string that defines an engine option, and determine
17376 // name, type, default value, and allowed value range
17377 {
17378         char *p, *q, buf[MSG_SIZ];
17379         int n, min = (-1)<<31, max = 1<<31, def;
17380
17381         opt->target = &opt->value;   // OK for spin/slider and checkbox
17382         if(p = strstr(opt->name, " -spin ")) {
17383             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17384             if(max < min) max = min; // enforce consistency
17385             if(def < min) def = min;
17386             if(def > max) def = max;
17387             opt->value = def;
17388             opt->min = min;
17389             opt->max = max;
17390             opt->type = Spin;
17391         } else if((p = strstr(opt->name, " -slider "))) {
17392             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17393             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17394             if(max < min) max = min; // enforce consistency
17395             if(def < min) def = min;
17396             if(def > max) def = max;
17397             opt->value = def;
17398             opt->min = min;
17399             opt->max = max;
17400             opt->type = Spin; // Slider;
17401         } else if((p = strstr(opt->name, " -string "))) {
17402             opt->textValue = p+9;
17403             opt->type = TextBox;
17404             opt->target = &opt->textValue;
17405         } else if((p = strstr(opt->name, " -file "))) {
17406             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17407             opt->target = opt->textValue = p+7;
17408             opt->type = FileName; // FileName;
17409             opt->target = &opt->textValue;
17410         } else if((p = strstr(opt->name, " -path "))) {
17411             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17412             opt->target = opt->textValue = p+7;
17413             opt->type = PathName; // PathName;
17414             opt->target = &opt->textValue;
17415         } else if(p = strstr(opt->name, " -check ")) {
17416             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17417             opt->value = (def != 0);
17418             opt->type = CheckBox;
17419         } else if(p = strstr(opt->name, " -combo ")) {
17420             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17421             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17422             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17423             opt->value = n = 0;
17424             while(q = StrStr(q, " /// ")) {
17425                 n++; *q = 0;    // count choices, and null-terminate each of them
17426                 q += 5;
17427                 if(*q == '*') { // remember default, which is marked with * prefix
17428                     q++;
17429                     opt->value = n;
17430                 }
17431                 cps->comboList[cps->comboCnt++] = q;
17432             }
17433             cps->comboList[cps->comboCnt++] = NULL;
17434             opt->max = n + 1;
17435             opt->type = ComboBox;
17436         } else if(p = strstr(opt->name, " -button")) {
17437             opt->type = Button;
17438         } else if(p = strstr(opt->name, " -save")) {
17439             opt->type = SaveButton;
17440         } else return FALSE;
17441         *p = 0; // terminate option name
17442         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17443         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17444         // now look if the command-line options define a setting for this engine option.
17445         if(cps->optionSettings && cps->optionSettings[0])
17446             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17447         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17448           snprintf(buf, MSG_SIZ, "option %s", p);
17449                 if(p = strstr(buf, ",")) *p = 0;
17450                 if(q = strchr(buf, '=')) switch(opt->type) {
17451                     case ComboBox:
17452                         for(n=0; n<opt->max; n++)
17453                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17454                         break;
17455                     case TextBox:
17456                     case FileName:
17457                     case PathName:
17458                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17459                         break;
17460                     case Spin:
17461                     case CheckBox:
17462                         opt->value = atoi(q+1);
17463                     default:
17464                         break;
17465                 }
17466                 strcat(buf, "\n");
17467                 SendToProgram(buf, cps);
17468         }
17469         return TRUE;
17470 }
17471
17472 void
17473 FeatureDone (ChessProgramState *cps, int val)
17474 {
17475   DelayedEventCallback cb = GetDelayedEvent();
17476   if ((cb == InitBackEnd3 && cps == &first) ||
17477       (cb == SettingsMenuIfReady && cps == &second) ||
17478       (cb == LoadEngine) || (cb == StartSecond) ||
17479       (cb == TwoMachinesEventIfReady)) {
17480     CancelDelayedEvent();
17481     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17482   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17483   cps->initDone = val;
17484   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17485 }
17486
17487 /* Parse feature command from engine */
17488 void
17489 ParseFeatures (char *args, ChessProgramState *cps)
17490 {
17491   char *p = args;
17492   char *q = NULL;
17493   int val;
17494   char buf[MSG_SIZ];
17495
17496   for (;;) {
17497     while (*p == ' ') p++;
17498     if (*p == NULLCHAR) return;
17499
17500     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17501     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17502     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17503     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17504     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17505     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17506     if (BoolFeature(&p, "reuse", &val, cps)) {
17507       /* Engine can disable reuse, but can't enable it if user said no */
17508       if (!val) cps->reuse = FALSE;
17509       continue;
17510     }
17511     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17512     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17513       if (gameMode == TwoMachinesPlay) {
17514         DisplayTwoMachinesTitle();
17515       } else {
17516         DisplayTitle("");
17517       }
17518       continue;
17519     }
17520     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17521     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17522     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17523     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17524     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17525     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17526     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17527     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17528     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17529     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17530     if (IntFeature(&p, "done", &val, cps)) {
17531       FeatureDone(cps, val);
17532       continue;
17533     }
17534     /* Added by Tord: */
17535     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17536     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17537     /* End of additions by Tord */
17538
17539     /* [HGM] added features: */
17540     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17541     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17542     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17543     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17544     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17545     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17546     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17547     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17548         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17549         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17550         FREE(cps->option[cps->nrOptions].name);
17551         cps->option[cps->nrOptions].name = q; q = NULL;
17552         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17553           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17554             SendToProgram(buf, cps);
17555             continue;
17556         }
17557         if(cps->nrOptions >= MAX_OPTIONS) {
17558             cps->nrOptions--;
17559             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17560             DisplayError(buf, 0);
17561         }
17562         continue;
17563     }
17564     /* End of additions by HGM */
17565
17566     /* unknown feature: complain and skip */
17567     q = p;
17568     while (*q && *q != '=') q++;
17569     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17570     SendToProgram(buf, cps);
17571     p = q;
17572     if (*p == '=') {
17573       p++;
17574       if (*p == '\"') {
17575         p++;
17576         while (*p && *p != '\"') p++;
17577         if (*p == '\"') p++;
17578       } else {
17579         while (*p && *p != ' ') p++;
17580       }
17581     }
17582   }
17583
17584 }
17585
17586 void
17587 PeriodicUpdatesEvent (int newState)
17588 {
17589     if (newState == appData.periodicUpdates)
17590       return;
17591
17592     appData.periodicUpdates=newState;
17593
17594     /* Display type changes, so update it now */
17595 //    DisplayAnalysis();
17596
17597     /* Get the ball rolling again... */
17598     if (newState) {
17599         AnalysisPeriodicEvent(1);
17600         StartAnalysisClock();
17601     }
17602 }
17603
17604 void
17605 PonderNextMoveEvent (int newState)
17606 {
17607     if (newState == appData.ponderNextMove) return;
17608     if (gameMode == EditPosition) EditPositionDone(TRUE);
17609     if (newState) {
17610         SendToProgram("hard\n", &first);
17611         if (gameMode == TwoMachinesPlay) {
17612             SendToProgram("hard\n", &second);
17613         }
17614     } else {
17615         SendToProgram("easy\n", &first);
17616         thinkOutput[0] = NULLCHAR;
17617         if (gameMode == TwoMachinesPlay) {
17618             SendToProgram("easy\n", &second);
17619         }
17620     }
17621     appData.ponderNextMove = newState;
17622 }
17623
17624 void
17625 NewSettingEvent (int option, int *feature, char *command, int value)
17626 {
17627     char buf[MSG_SIZ];
17628
17629     if (gameMode == EditPosition) EditPositionDone(TRUE);
17630     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17631     if(feature == NULL || *feature) SendToProgram(buf, &first);
17632     if (gameMode == TwoMachinesPlay) {
17633         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17634     }
17635 }
17636
17637 void
17638 ShowThinkingEvent ()
17639 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17640 {
17641     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17642     int newState = appData.showThinking
17643         // [HGM] thinking: other features now need thinking output as well
17644         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17645
17646     if (oldState == newState) return;
17647     oldState = newState;
17648     if (gameMode == EditPosition) EditPositionDone(TRUE);
17649     if (oldState) {
17650         SendToProgram("post\n", &first);
17651         if (gameMode == TwoMachinesPlay) {
17652             SendToProgram("post\n", &second);
17653         }
17654     } else {
17655         SendToProgram("nopost\n", &first);
17656         thinkOutput[0] = NULLCHAR;
17657         if (gameMode == TwoMachinesPlay) {
17658             SendToProgram("nopost\n", &second);
17659         }
17660     }
17661 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17662 }
17663
17664 void
17665 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17666 {
17667   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17668   if (pr == NoProc) return;
17669   AskQuestion(title, question, replyPrefix, pr);
17670 }
17671
17672 void
17673 TypeInEvent (char firstChar)
17674 {
17675     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17676         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17677         gameMode == AnalyzeMode || gameMode == EditGame ||
17678         gameMode == EditPosition || gameMode == IcsExamining ||
17679         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17680         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17681                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17682                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17683         gameMode == Training) PopUpMoveDialog(firstChar);
17684 }
17685
17686 void
17687 TypeInDoneEvent (char *move)
17688 {
17689         Board board;
17690         int n, fromX, fromY, toX, toY;
17691         char promoChar;
17692         ChessMove moveType;
17693
17694         // [HGM] FENedit
17695         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17696                 EditPositionPasteFEN(move);
17697                 return;
17698         }
17699         // [HGM] movenum: allow move number to be typed in any mode
17700         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17701           ToNrEvent(2*n-1);
17702           return;
17703         }
17704         // undocumented kludge: allow command-line option to be typed in!
17705         // (potentially fatal, and does not implement the effect of the option.)
17706         // should only be used for options that are values on which future decisions will be made,
17707         // and definitely not on options that would be used during initialization.
17708         if(strstr(move, "!!! -") == move) {
17709             ParseArgsFromString(move+4);
17710             return;
17711         }
17712
17713       if (gameMode != EditGame && currentMove != forwardMostMove &&
17714         gameMode != Training) {
17715         DisplayMoveError(_("Displayed move is not current"));
17716       } else {
17717         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17718           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17719         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17720         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17721           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17722           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17723         } else {
17724           DisplayMoveError(_("Could not parse move"));
17725         }
17726       }
17727 }
17728
17729 void
17730 DisplayMove (int moveNumber)
17731 {
17732     char message[MSG_SIZ];
17733     char res[MSG_SIZ];
17734     char cpThinkOutput[MSG_SIZ];
17735
17736     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17737
17738     if (moveNumber == forwardMostMove - 1 ||
17739         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17740
17741         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17742
17743         if (strchr(cpThinkOutput, '\n')) {
17744             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17745         }
17746     } else {
17747         *cpThinkOutput = NULLCHAR;
17748     }
17749
17750     /* [AS] Hide thinking from human user */
17751     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17752         *cpThinkOutput = NULLCHAR;
17753         if( thinkOutput[0] != NULLCHAR ) {
17754             int i;
17755
17756             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17757                 cpThinkOutput[i] = '.';
17758             }
17759             cpThinkOutput[i] = NULLCHAR;
17760             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17761         }
17762     }
17763
17764     if (moveNumber == forwardMostMove - 1 &&
17765         gameInfo.resultDetails != NULL) {
17766         if (gameInfo.resultDetails[0] == NULLCHAR) {
17767           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17768         } else {
17769           snprintf(res, MSG_SIZ, " {%s} %s",
17770                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17771         }
17772     } else {
17773         res[0] = NULLCHAR;
17774     }
17775
17776     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17777         DisplayMessage(res, cpThinkOutput);
17778     } else {
17779       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17780                 WhiteOnMove(moveNumber) ? " " : ".. ",
17781                 parseList[moveNumber], res);
17782         DisplayMessage(message, cpThinkOutput);
17783     }
17784 }
17785
17786 void
17787 DisplayComment (int moveNumber, char *text)
17788 {
17789     char title[MSG_SIZ];
17790
17791     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17792       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17793     } else {
17794       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17795               WhiteOnMove(moveNumber) ? " " : ".. ",
17796               parseList[moveNumber]);
17797     }
17798     if (text != NULL && (appData.autoDisplayComment || commentUp))
17799         CommentPopUp(title, text);
17800 }
17801
17802 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17803  * might be busy thinking or pondering.  It can be omitted if your
17804  * gnuchess is configured to stop thinking immediately on any user
17805  * input.  However, that gnuchess feature depends on the FIONREAD
17806  * ioctl, which does not work properly on some flavors of Unix.
17807  */
17808 void
17809 Attention (ChessProgramState *cps)
17810 {
17811 #if ATTENTION
17812     if (!cps->useSigint) return;
17813     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17814     switch (gameMode) {
17815       case MachinePlaysWhite:
17816       case MachinePlaysBlack:
17817       case TwoMachinesPlay:
17818       case IcsPlayingWhite:
17819       case IcsPlayingBlack:
17820       case AnalyzeMode:
17821       case AnalyzeFile:
17822         /* Skip if we know it isn't thinking */
17823         if (!cps->maybeThinking) return;
17824         if (appData.debugMode)
17825           fprintf(debugFP, "Interrupting %s\n", cps->which);
17826         InterruptChildProcess(cps->pr);
17827         cps->maybeThinking = FALSE;
17828         break;
17829       default:
17830         break;
17831     }
17832 #endif /*ATTENTION*/
17833 }
17834
17835 int
17836 CheckFlags ()
17837 {
17838     if (whiteTimeRemaining <= 0) {
17839         if (!whiteFlag) {
17840             whiteFlag = TRUE;
17841             if (appData.icsActive) {
17842                 if (appData.autoCallFlag &&
17843                     gameMode == IcsPlayingBlack && !blackFlag) {
17844                   SendToICS(ics_prefix);
17845                   SendToICS("flag\n");
17846                 }
17847             } else {
17848                 if (blackFlag) {
17849                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17850                 } else {
17851                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17852                     if (appData.autoCallFlag) {
17853                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17854                         return TRUE;
17855                     }
17856                 }
17857             }
17858         }
17859     }
17860     if (blackTimeRemaining <= 0) {
17861         if (!blackFlag) {
17862             blackFlag = TRUE;
17863             if (appData.icsActive) {
17864                 if (appData.autoCallFlag &&
17865                     gameMode == IcsPlayingWhite && !whiteFlag) {
17866                   SendToICS(ics_prefix);
17867                   SendToICS("flag\n");
17868                 }
17869             } else {
17870                 if (whiteFlag) {
17871                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17872                 } else {
17873                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17874                     if (appData.autoCallFlag) {
17875                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17876                         return TRUE;
17877                     }
17878                 }
17879             }
17880         }
17881     }
17882     return FALSE;
17883 }
17884
17885 void
17886 CheckTimeControl ()
17887 {
17888     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17889         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17890
17891     /*
17892      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17893      */
17894     if ( !WhiteOnMove(forwardMostMove) ) {
17895         /* White made time control */
17896         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17897         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17898         /* [HGM] time odds: correct new time quota for time odds! */
17899                                             / WhitePlayer()->timeOdds;
17900         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17901     } else {
17902         lastBlack -= blackTimeRemaining;
17903         /* Black made time control */
17904         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17905                                             / WhitePlayer()->other->timeOdds;
17906         lastWhite = whiteTimeRemaining;
17907     }
17908 }
17909
17910 void
17911 DisplayBothClocks ()
17912 {
17913     int wom = gameMode == EditPosition ?
17914       !blackPlaysFirst : WhiteOnMove(currentMove);
17915     DisplayWhiteClock(whiteTimeRemaining, wom);
17916     DisplayBlackClock(blackTimeRemaining, !wom);
17917 }
17918
17919
17920 /* Timekeeping seems to be a portability nightmare.  I think everyone
17921    has ftime(), but I'm really not sure, so I'm including some ifdefs
17922    to use other calls if you don't.  Clocks will be less accurate if
17923    you have neither ftime nor gettimeofday.
17924 */
17925
17926 /* VS 2008 requires the #include outside of the function */
17927 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17928 #include <sys/timeb.h>
17929 #endif
17930
17931 /* Get the current time as a TimeMark */
17932 void
17933 GetTimeMark (TimeMark *tm)
17934 {
17935 #if HAVE_GETTIMEOFDAY
17936
17937     struct timeval timeVal;
17938     struct timezone timeZone;
17939
17940     gettimeofday(&timeVal, &timeZone);
17941     tm->sec = (long) timeVal.tv_sec;
17942     tm->ms = (int) (timeVal.tv_usec / 1000L);
17943
17944 #else /*!HAVE_GETTIMEOFDAY*/
17945 #if HAVE_FTIME
17946
17947 // include <sys/timeb.h> / moved to just above start of function
17948     struct timeb timeB;
17949
17950     ftime(&timeB);
17951     tm->sec = (long) timeB.time;
17952     tm->ms = (int) timeB.millitm;
17953
17954 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17955     tm->sec = (long) time(NULL);
17956     tm->ms = 0;
17957 #endif
17958 #endif
17959 }
17960
17961 /* Return the difference in milliseconds between two
17962    time marks.  We assume the difference will fit in a long!
17963 */
17964 long
17965 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17966 {
17967     return 1000L*(tm2->sec - tm1->sec) +
17968            (long) (tm2->ms - tm1->ms);
17969 }
17970
17971
17972 /*
17973  * Code to manage the game clocks.
17974  *
17975  * In tournament play, black starts the clock and then white makes a move.
17976  * We give the human user a slight advantage if he is playing white---the
17977  * clocks don't run until he makes his first move, so it takes zero time.
17978  * Also, we don't account for network lag, so we could get out of sync
17979  * with GNU Chess's clock -- but then, referees are always right.
17980  */
17981
17982 static TimeMark tickStartTM;
17983 static long intendedTickLength;
17984
17985 long
17986 NextTickLength (long timeRemaining)
17987 {
17988     long nominalTickLength, nextTickLength;
17989
17990     if (timeRemaining > 0L && timeRemaining <= 10000L)
17991       nominalTickLength = 100L;
17992     else
17993       nominalTickLength = 1000L;
17994     nextTickLength = timeRemaining % nominalTickLength;
17995     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17996
17997     return nextTickLength;
17998 }
17999
18000 /* Adjust clock one minute up or down */
18001 void
18002 AdjustClock (Boolean which, int dir)
18003 {
18004     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
18005     if(which) blackTimeRemaining += 60000*dir;
18006     else      whiteTimeRemaining += 60000*dir;
18007     DisplayBothClocks();
18008     adjustedClock = TRUE;
18009 }
18010
18011 /* Stop clocks and reset to a fresh time control */
18012 void
18013 ResetClocks ()
18014 {
18015     (void) StopClockTimer();
18016     if (appData.icsActive) {
18017         whiteTimeRemaining = blackTimeRemaining = 0;
18018     } else if (searchTime) {
18019         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18020         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18021     } else { /* [HGM] correct new time quote for time odds */
18022         whiteTC = blackTC = fullTimeControlString;
18023         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18024         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18025     }
18026     if (whiteFlag || blackFlag) {
18027         DisplayTitle("");
18028         whiteFlag = blackFlag = FALSE;
18029     }
18030     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18031     DisplayBothClocks();
18032     adjustedClock = FALSE;
18033 }
18034
18035 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18036
18037 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18038
18039 /* Decrement running clock by amount of time that has passed */
18040 void
18041 DecrementClocks ()
18042 {
18043     long tRemaining;
18044     long lastTickLength, fudge;
18045     TimeMark now;
18046
18047     if (!appData.clockMode) return;
18048     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18049
18050     GetTimeMark(&now);
18051
18052     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18053
18054     /* Fudge if we woke up a little too soon */
18055     fudge = intendedTickLength - lastTickLength;
18056     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18057
18058     if (WhiteOnMove(forwardMostMove)) {
18059         if(whiteNPS >= 0) lastTickLength = 0;
18060          tRemaining = whiteTimeRemaining -= lastTickLength;
18061         if( tRemaining < 0 && !appData.icsActive) {
18062             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18063             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18064                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18065                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18066             }
18067         }
18068         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18069         DisplayWhiteClock(whiteTimeRemaining - fudge,
18070                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18071         timeSuffix = 0;
18072     } else {
18073         if(blackNPS >= 0) lastTickLength = 0;
18074          tRemaining = blackTimeRemaining -= lastTickLength;
18075         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18076             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18077             if(suddenDeath) {
18078                 blackStartMove = forwardMostMove;
18079                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18080             }
18081         }
18082         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18083         DisplayBlackClock(blackTimeRemaining - fudge,
18084                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18085         timeSuffix = 0;
18086     }
18087     if (CheckFlags()) return;
18088
18089     if(twoBoards) { // count down secondary board's clocks as well
18090         activePartnerTime -= lastTickLength;
18091         partnerUp = 1;
18092         if(activePartner == 'W')
18093             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18094         else
18095             DisplayBlackClock(activePartnerTime, TRUE);
18096         partnerUp = 0;
18097     }
18098
18099     tickStartTM = now;
18100     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18101     StartClockTimer(intendedTickLength);
18102
18103     /* if the time remaining has fallen below the alarm threshold, sound the
18104      * alarm. if the alarm has sounded and (due to a takeback or time control
18105      * with increment) the time remaining has increased to a level above the
18106      * threshold, reset the alarm so it can sound again.
18107      */
18108
18109     if (appData.icsActive && appData.icsAlarm) {
18110
18111         /* make sure we are dealing with the user's clock */
18112         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18113                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18114            )) return;
18115
18116         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18117             alarmSounded = FALSE;
18118         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18119             PlayAlarmSound();
18120             alarmSounded = TRUE;
18121         }
18122     }
18123 }
18124
18125
18126 /* A player has just moved, so stop the previously running
18127    clock and (if in clock mode) start the other one.
18128    We redisplay both clocks in case we're in ICS mode, because
18129    ICS gives us an update to both clocks after every move.
18130    Note that this routine is called *after* forwardMostMove
18131    is updated, so the last fractional tick must be subtracted
18132    from the color that is *not* on move now.
18133 */
18134 void
18135 SwitchClocks (int newMoveNr)
18136 {
18137     long lastTickLength;
18138     TimeMark now;
18139     int flagged = FALSE;
18140
18141     GetTimeMark(&now);
18142
18143     if (StopClockTimer() && appData.clockMode) {
18144         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18145         if (!WhiteOnMove(forwardMostMove)) {
18146             if(blackNPS >= 0) lastTickLength = 0;
18147             blackTimeRemaining -= lastTickLength;
18148            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18149 //         if(pvInfoList[forwardMostMove].time == -1)
18150                  pvInfoList[forwardMostMove].time =               // use GUI time
18151                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18152         } else {
18153            if(whiteNPS >= 0) lastTickLength = 0;
18154            whiteTimeRemaining -= lastTickLength;
18155            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18156 //         if(pvInfoList[forwardMostMove].time == -1)
18157                  pvInfoList[forwardMostMove].time =
18158                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18159         }
18160         flagged = CheckFlags();
18161     }
18162     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18163     CheckTimeControl();
18164
18165     if (flagged || !appData.clockMode) return;
18166
18167     switch (gameMode) {
18168       case MachinePlaysBlack:
18169       case MachinePlaysWhite:
18170       case BeginningOfGame:
18171         if (pausing) return;
18172         break;
18173
18174       case EditGame:
18175       case PlayFromGameFile:
18176       case IcsExamining:
18177         return;
18178
18179       default:
18180         break;
18181     }
18182
18183     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18184         if(WhiteOnMove(forwardMostMove))
18185              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18186         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18187     }
18188
18189     tickStartTM = now;
18190     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18191       whiteTimeRemaining : blackTimeRemaining);
18192     StartClockTimer(intendedTickLength);
18193 }
18194
18195
18196 /* Stop both clocks */
18197 void
18198 StopClocks ()
18199 {
18200     long lastTickLength;
18201     TimeMark now;
18202
18203     if (!StopClockTimer()) return;
18204     if (!appData.clockMode) return;
18205
18206     GetTimeMark(&now);
18207
18208     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18209     if (WhiteOnMove(forwardMostMove)) {
18210         if(whiteNPS >= 0) lastTickLength = 0;
18211         whiteTimeRemaining -= lastTickLength;
18212         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18213     } else {
18214         if(blackNPS >= 0) lastTickLength = 0;
18215         blackTimeRemaining -= lastTickLength;
18216         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18217     }
18218     CheckFlags();
18219 }
18220
18221 /* Start clock of player on move.  Time may have been reset, so
18222    if clock is already running, stop and restart it. */
18223 void
18224 StartClocks ()
18225 {
18226     (void) StopClockTimer(); /* in case it was running already */
18227     DisplayBothClocks();
18228     if (CheckFlags()) return;
18229
18230     if (!appData.clockMode) return;
18231     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18232
18233     GetTimeMark(&tickStartTM);
18234     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18235       whiteTimeRemaining : blackTimeRemaining);
18236
18237    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18238     whiteNPS = blackNPS = -1;
18239     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18240        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18241         whiteNPS = first.nps;
18242     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18243        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18244         blackNPS = first.nps;
18245     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18246         whiteNPS = second.nps;
18247     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18248         blackNPS = second.nps;
18249     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18250
18251     StartClockTimer(intendedTickLength);
18252 }
18253
18254 char *
18255 TimeString (long ms)
18256 {
18257     long second, minute, hour, day;
18258     char *sign = "";
18259     static char buf[40], moveTime[8];
18260
18261     if (ms > 0 && ms <= 9900) {
18262       /* convert milliseconds to tenths, rounding up */
18263       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18264
18265       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18266       return buf;
18267     }
18268
18269     /* convert milliseconds to seconds, rounding up */
18270     /* use floating point to avoid strangeness of integer division
18271        with negative dividends on many machines */
18272     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18273
18274     if (second < 0) {
18275         sign = "-";
18276         second = -second;
18277     }
18278
18279     day = second / (60 * 60 * 24);
18280     second = second % (60 * 60 * 24);
18281     hour = second / (60 * 60);
18282     second = second % (60 * 60);
18283     minute = second / 60;
18284     second = second % 60;
18285
18286     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18287     else *moveTime = NULLCHAR;
18288
18289     if (day > 0)
18290       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18291               sign, day, hour, minute, second, moveTime);
18292     else if (hour > 0)
18293       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18294     else
18295       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18296
18297     return buf;
18298 }
18299
18300
18301 /*
18302  * This is necessary because some C libraries aren't ANSI C compliant yet.
18303  */
18304 char *
18305 StrStr (char *string, char *match)
18306 {
18307     int i, length;
18308
18309     length = strlen(match);
18310
18311     for (i = strlen(string) - length; i >= 0; i--, string++)
18312       if (!strncmp(match, string, length))
18313         return string;
18314
18315     return NULL;
18316 }
18317
18318 char *
18319 StrCaseStr (char *string, char *match)
18320 {
18321     int i, j, length;
18322
18323     length = strlen(match);
18324
18325     for (i = strlen(string) - length; i >= 0; i--, string++) {
18326         for (j = 0; j < length; j++) {
18327             if (ToLower(match[j]) != ToLower(string[j]))
18328               break;
18329         }
18330         if (j == length) return string;
18331     }
18332
18333     return NULL;
18334 }
18335
18336 #ifndef _amigados
18337 int
18338 StrCaseCmp (char *s1, char *s2)
18339 {
18340     char c1, c2;
18341
18342     for (;;) {
18343         c1 = ToLower(*s1++);
18344         c2 = ToLower(*s2++);
18345         if (c1 > c2) return 1;
18346         if (c1 < c2) return -1;
18347         if (c1 == NULLCHAR) return 0;
18348     }
18349 }
18350
18351
18352 int
18353 ToLower (int c)
18354 {
18355     return isupper(c) ? tolower(c) : c;
18356 }
18357
18358
18359 int
18360 ToUpper (int c)
18361 {
18362     return islower(c) ? toupper(c) : c;
18363 }
18364 #endif /* !_amigados    */
18365
18366 char *
18367 StrSave (char *s)
18368 {
18369   char *ret;
18370
18371   if ((ret = (char *) malloc(strlen(s) + 1)))
18372     {
18373       safeStrCpy(ret, s, strlen(s)+1);
18374     }
18375   return ret;
18376 }
18377
18378 char *
18379 StrSavePtr (char *s, char **savePtr)
18380 {
18381     if (*savePtr) {
18382         free(*savePtr);
18383     }
18384     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18385       safeStrCpy(*savePtr, s, strlen(s)+1);
18386     }
18387     return(*savePtr);
18388 }
18389
18390 char *
18391 PGNDate ()
18392 {
18393     time_t clock;
18394     struct tm *tm;
18395     char buf[MSG_SIZ];
18396
18397     clock = time((time_t *)NULL);
18398     tm = localtime(&clock);
18399     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18400             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18401     return StrSave(buf);
18402 }
18403
18404
18405 char *
18406 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18407 {
18408     int i, j, fromX, fromY, toX, toY;
18409     int whiteToPlay, haveRights = nrCastlingRights;
18410     char buf[MSG_SIZ];
18411     char *p, *q;
18412     int emptycount;
18413     ChessSquare piece;
18414
18415     whiteToPlay = (gameMode == EditPosition) ?
18416       !blackPlaysFirst : (move % 2 == 0);
18417     p = buf;
18418
18419     /* Piece placement data */
18420     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18421         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18422         emptycount = 0;
18423         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18424             if (boards[move][i][j] == EmptySquare) {
18425                 emptycount++;
18426             } else { ChessSquare piece = boards[move][i][j];
18427                 if (emptycount > 0) {
18428                     if(emptycount<10) /* [HGM] can be >= 10 */
18429                         *p++ = '0' + emptycount;
18430                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18431                     emptycount = 0;
18432                 }
18433                 if(PieceToChar(piece) == '+') {
18434                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18435                     *p++ = '+';
18436                     piece = (ChessSquare)(CHUDEMOTED(piece));
18437                 }
18438                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18439                 if(*p = PieceSuffix(piece)) p++;
18440                 if(p[-1] == '~') {
18441                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18442                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18443                     *p++ = '~';
18444                 }
18445             }
18446         }
18447         if (emptycount > 0) {
18448             if(emptycount<10) /* [HGM] can be >= 10 */
18449                 *p++ = '0' + emptycount;
18450             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18451             emptycount = 0;
18452         }
18453         *p++ = '/';
18454     }
18455     *(p - 1) = ' ';
18456
18457     /* [HGM] print Crazyhouse or Shogi holdings */
18458     if( gameInfo.holdingsWidth ) {
18459         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18460         q = p;
18461         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18462             piece = boards[move][i][BOARD_WIDTH-1];
18463             if( piece != EmptySquare )
18464               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18465                   *p++ = PieceToChar(piece);
18466         }
18467         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18468             piece = boards[move][handSize-i-1][0];
18469             if( piece != EmptySquare )
18470               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18471                   *p++ = PieceToChar(piece);
18472         }
18473
18474         if( q == p ) *p++ = '-';
18475         *p++ = ']';
18476         *p++ = ' ';
18477     }
18478
18479     /* Active color */
18480     *p++ = whiteToPlay ? 'w' : 'b';
18481     *p++ = ' ';
18482
18483   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18484     haveRights = 0; q = p;
18485     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18486       piece = boards[move][0][i];
18487       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18488         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18489       }
18490     }
18491     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18492       piece = boards[move][BOARD_HEIGHT-1][i];
18493       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18494         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18495       }
18496     }
18497     if(p == q) *p++ = '-';
18498     *p++ = ' ';
18499   }
18500
18501   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18502     while(*p++ = *q++)
18503                       ;
18504     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18505   } else {
18506   if(haveRights) {
18507      int handW=0, handB=0;
18508      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18509         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18510         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18511      }
18512      q = p;
18513      if(appData.fischerCastling) {
18514         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18515            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18516                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18517         } else {
18518        /* [HGM] write directly from rights */
18519            if(boards[move][CASTLING][2] != NoRights &&
18520               boards[move][CASTLING][0] != NoRights   )
18521                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18522            if(boards[move][CASTLING][2] != NoRights &&
18523               boards[move][CASTLING][1] != NoRights   )
18524                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18525         }
18526         if(handB) {
18527            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18528                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18529         } else {
18530            if(boards[move][CASTLING][5] != NoRights &&
18531               boards[move][CASTLING][3] != NoRights   )
18532                 *p++ = boards[move][CASTLING][3] + AAA;
18533            if(boards[move][CASTLING][5] != NoRights &&
18534               boards[move][CASTLING][4] != NoRights   )
18535                 *p++ = boards[move][CASTLING][4] + AAA;
18536         }
18537      } else {
18538
18539         /* [HGM] write true castling rights */
18540         if( nrCastlingRights == 6 ) {
18541             int q, k=0;
18542             if(boards[move][CASTLING][0] != NoRights &&
18543                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18544             q = (boards[move][CASTLING][1] != NoRights &&
18545                  boards[move][CASTLING][2] != NoRights  );
18546             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18547                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18548                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18549                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18550             }
18551             if(q) *p++ = 'Q';
18552             k = 0;
18553             if(boards[move][CASTLING][3] != NoRights &&
18554                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18555             q = (boards[move][CASTLING][4] != NoRights &&
18556                  boards[move][CASTLING][5] != NoRights  );
18557             if(handB) {
18558                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18559                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18560                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18561             }
18562             if(q) *p++ = 'q';
18563         }
18564      }
18565      if (q == p) *p++ = '-'; /* No castling rights */
18566      *p++ = ' ';
18567   }
18568
18569   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18570      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18571      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18572     /* En passant target square */
18573     if (move > backwardMostMove) {
18574         fromX = moveList[move - 1][0] - AAA;
18575         fromY = moveList[move - 1][1] - ONE;
18576         toX = moveList[move - 1][2] - AAA;
18577         toY = moveList[move - 1][3] - ONE;
18578         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18579             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18580             /* 2-square pawn move just happened */
18581             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18582             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18583             if(gameInfo.variant == VariantBerolina) {
18584                 *p++ = toX + AAA;
18585                 *p++ = toY + ONE;
18586             }
18587         } else {
18588             *p++ = '-';
18589         }
18590     } else if(move == backwardMostMove) {
18591         // [HGM] perhaps we should always do it like this, and forget the above?
18592         if((signed char)boards[move][EP_STATUS] >= 0) {
18593             *p++ = boards[move][EP_STATUS] + AAA;
18594             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18595         } else {
18596             *p++ = '-';
18597         }
18598     } else {
18599         *p++ = '-';
18600     }
18601     *p++ = ' ';
18602   }
18603   }
18604
18605     i = boards[move][CHECK_COUNT];
18606     if(i) {
18607         sprintf(p, "%d+%d ", i&255, i>>8);
18608         while(*p) p++;
18609     }
18610
18611     if(moveCounts)
18612     {   int i = 0, j=move;
18613
18614         /* [HGM] find reversible plies */
18615         if (appData.debugMode) { int k;
18616             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18617             for(k=backwardMostMove; k<=forwardMostMove; k++)
18618                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18619
18620         }
18621
18622         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18623         if( j == backwardMostMove ) i += initialRulePlies;
18624         sprintf(p, "%d ", i);
18625         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18626
18627         /* Fullmove number */
18628         sprintf(p, "%d", (move / 2) + 1);
18629     } else *--p = NULLCHAR;
18630
18631     return StrSave(buf);
18632 }
18633
18634 Boolean
18635 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18636 {
18637     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18638     char *p, c;
18639     int emptycount, virgin[BOARD_FILES];
18640     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18641
18642     p = fen;
18643
18644     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18645
18646     /* Piece placement data */
18647     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18648         j = 0;
18649         for (;;) {
18650             if (*p == '/' || *p == ' ' || *p == '[' ) {
18651                 if(j > w) w = j;
18652                 emptycount = gameInfo.boardWidth - j;
18653                 while (emptycount--)
18654                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18655                 if (*p == '/') p++;
18656                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18657                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18658                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18659                     }
18660                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18661                 }
18662                 break;
18663 #if(BOARD_FILES >= 10)*0
18664             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18665                 p++; emptycount=10;
18666                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18667                 while (emptycount--)
18668                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18669 #endif
18670             } else if (*p == '*') {
18671                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18672             } else if (isdigit(*p)) {
18673                 emptycount = *p++ - '0';
18674                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18675                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18676                 while (emptycount--)
18677                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18678             } else if (*p == '<') {
18679                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18680                 else if (i != 0 || !shuffle) return FALSE;
18681                 p++;
18682             } else if (shuffle && *p == '>') {
18683                 p++; // for now ignore closing shuffle range, and assume rank-end
18684             } else if (*p == '?') {
18685                 if (j >= gameInfo.boardWidth) return FALSE;
18686                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18687                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18688             } else if (*p == '+' || isalpha(*p)) {
18689                 char *q, *s = SUFFIXES;
18690                 if (j >= gameInfo.boardWidth) return FALSE;
18691                 if(*p=='+') {
18692                     char c = *++p;
18693                     if(q = strchr(s, p[1])) p++;
18694                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18695                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18696                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18697                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18698                 } else {
18699                     char c = *p++;
18700                     if(q = strchr(s, *p)) p++;
18701                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18702                 }
18703
18704                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18705                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18706                     piece = (ChessSquare) (PROMOTED(piece));
18707                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18708                     p++;
18709                 }
18710                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18711                 if(piece == king) wKingRank = i;
18712                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18713             } else {
18714                 return FALSE;
18715             }
18716         }
18717     }
18718     while (*p == '/' || *p == ' ') p++;
18719
18720     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18721
18722     /* [HGM] by default clear Crazyhouse holdings, if present */
18723     if(gameInfo.holdingsWidth) {
18724        for(i=0; i<handSize; i++) {
18725            board[i][0]             = EmptySquare; /* black holdings */
18726            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18727            board[i][1]             = (ChessSquare) 0; /* black counts */
18728            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18729        }
18730     }
18731
18732     /* [HGM] look for Crazyhouse holdings here */
18733     while(*p==' ') p++;
18734     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18735         int swap=0, wcnt=0, bcnt=0;
18736         if(*p == '[') p++;
18737         if(*p == '<') swap++, p++;
18738         if(*p == '-' ) p++; /* empty holdings */ else {
18739             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18740             /* if we would allow FEN reading to set board size, we would   */
18741             /* have to add holdings and shift the board read so far here   */
18742             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18743                 p++;
18744                 if((int) piece >= (int) BlackPawn ) {
18745                     i = (int)piece - (int)BlackPawn;
18746                     i = PieceToNumber((ChessSquare)i);
18747                     if( i >= gameInfo.holdingsSize ) return FALSE;
18748                     board[handSize-1-i][0] = piece; /* black holdings */
18749                     board[handSize-1-i][1]++;       /* black counts   */
18750                     bcnt++;
18751                 } else {
18752                     i = (int)piece - (int)WhitePawn;
18753                     i = PieceToNumber((ChessSquare)i);
18754                     if( i >= gameInfo.holdingsSize ) return FALSE;
18755                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18756                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18757                     wcnt++;
18758                 }
18759             }
18760             if(subst) { // substitute back-rank question marks by holdings pieces
18761                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18762                     int k, m, n = bcnt + 1;
18763                     if(board[0][j] == ClearBoard) {
18764                         if(!wcnt) return FALSE;
18765                         n = rand() % wcnt;
18766                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18767                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18768                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18769                             break;
18770                         }
18771                     }
18772                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18773                         if(!bcnt) return FALSE;
18774                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18775                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18776                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18777                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18778                             break;
18779                         }
18780                     }
18781                 }
18782                 subst = 0;
18783             }
18784         }
18785         if(*p == ']') p++;
18786     }
18787
18788     if(subst) return FALSE; // substitution requested, but no holdings
18789
18790     while(*p == ' ') p++;
18791
18792     /* Active color */
18793     c = *p++;
18794     if(appData.colorNickNames) {
18795       if( c == appData.colorNickNames[0] ) c = 'w'; else
18796       if( c == appData.colorNickNames[1] ) c = 'b';
18797     }
18798     switch (c) {
18799       case 'w':
18800         *blackPlaysFirst = FALSE;
18801         break;
18802       case 'b':
18803         *blackPlaysFirst = TRUE;
18804         break;
18805       default:
18806         return FALSE;
18807     }
18808
18809     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18810     /* return the extra info in global variiables             */
18811
18812     while(*p==' ') p++;
18813
18814     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18815         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18816         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18817     }
18818
18819     /* set defaults in case FEN is incomplete */
18820     board[EP_STATUS] = EP_UNKNOWN;
18821     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18822     for(i=0; i<nrCastlingRights; i++ ) {
18823         board[CASTLING][i] =
18824             appData.fischerCastling ? NoRights : initialRights[i];
18825     }   /* assume possible unless obviously impossible */
18826     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18827     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18828     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18829                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18830     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18831     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18832     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18833                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18834     FENrulePlies = 0;
18835
18836     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18837       char *q = p;
18838       int w=0, b=0;
18839       while(isalpha(*p)) {
18840         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18841         if(islower(*p)) b |= 1 << (*p++ - 'a');
18842       }
18843       if(*p == '-') p++;
18844       if(p != q) {
18845         board[TOUCHED_W] = ~w;
18846         board[TOUCHED_B] = ~b;
18847         while(*p == ' ') p++;
18848       }
18849     } else
18850
18851     if(nrCastlingRights) {
18852       int fischer = 0;
18853       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18854       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18855           /* castling indicator present, so default becomes no castlings */
18856           for(i=0; i<nrCastlingRights; i++ ) {
18857                  board[CASTLING][i] = NoRights;
18858           }
18859       }
18860       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18861              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18862              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18863              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18864         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18865
18866         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18867             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18868             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18869         }
18870         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18871             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18872         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18873                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18874         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18875                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18876         switch(c) {
18877           case'K':
18878               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18879               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18880               board[CASTLING][2] = whiteKingFile;
18881               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18882               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18883               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18884               break;
18885           case'Q':
18886               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18887               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18888               board[CASTLING][2] = whiteKingFile;
18889               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18890               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18891               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18892               break;
18893           case'k':
18894               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18895               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18896               board[CASTLING][5] = blackKingFile;
18897               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18898               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18899               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18900               break;
18901           case'q':
18902               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18903               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18904               board[CASTLING][5] = blackKingFile;
18905               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18906               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18907               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18908           case '-':
18909               break;
18910           default: /* FRC castlings */
18911               if(c >= 'a') { /* black rights */
18912                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18913                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18914                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18915                   if(i == BOARD_RGHT) break;
18916                   board[CASTLING][5] = i;
18917                   c -= AAA;
18918                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18919                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18920                   if(c > i)
18921                       board[CASTLING][3] = c;
18922                   else
18923                       board[CASTLING][4] = c;
18924               } else { /* white rights */
18925                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18926                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18927                     if(board[0][i] == WhiteKing) break;
18928                   if(i == BOARD_RGHT) break;
18929                   board[CASTLING][2] = i;
18930                   c -= AAA - 'a' + 'A';
18931                   if(board[0][c] >= WhiteKing) break;
18932                   if(c > i)
18933                       board[CASTLING][0] = c;
18934                   else
18935                       board[CASTLING][1] = c;
18936               }
18937         }
18938       }
18939       for(i=0; i<nrCastlingRights; i++)
18940         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18941       if(gameInfo.variant == VariantSChess)
18942         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18943       if(fischer && shuffle) appData.fischerCastling = TRUE;
18944     if (appData.debugMode) {
18945         fprintf(debugFP, "FEN castling rights:");
18946         for(i=0; i<nrCastlingRights; i++)
18947         fprintf(debugFP, " %d", board[CASTLING][i]);
18948         fprintf(debugFP, "\n");
18949     }
18950
18951       while(*p==' ') p++;
18952     }
18953
18954     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18955
18956     /* read e.p. field in games that know e.p. capture */
18957     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18958        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18959        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18960       if(*p=='-') {
18961         p++; board[EP_STATUS] = EP_NONE;
18962       } else {
18963          int d, r, c = *p - AAA;
18964
18965          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18966              p++;
18967              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18968              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18969              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18970              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18971              board[LAST_TO] = 256*(r + d) + c;
18972              c = *p++ - AAA;
18973              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18974                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18975                  board[LAST_TO] = 256*r + c;
18976                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18977              }
18978          }
18979       }
18980     }
18981
18982     while(*p == ' ') p++;
18983
18984     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18985     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18986         board[CHECK_COUNT] = i + 256*j;
18987         while(*p && *p != ' ') p++;
18988     }
18989
18990     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18991     if(c > 0) {
18992         FENrulePlies = i; /* 50-move ply counter */
18993         /* (The move number is still ignored)    */
18994         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18995     }
18996
18997     return TRUE;
18998 }
18999
19000 void
19001 EditPositionPasteFEN (char *fen)
19002 {
19003   if (fen != NULL) {
19004     Board initial_position;
19005
19006     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
19007       DisplayError(_("Bad FEN position in clipboard"), 0);
19008       return ;
19009     } else {
19010       int savedBlackPlaysFirst = blackPlaysFirst;
19011       EditPositionEvent();
19012       blackPlaysFirst = savedBlackPlaysFirst;
19013       CopyBoard(boards[0], initial_position);
19014       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19015       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19016       DisplayBothClocks();
19017       DrawPosition(FALSE, boards[currentMove]);
19018     }
19019   }
19020 }
19021
19022 static char cseq[12] = "\\   ";
19023
19024 Boolean
19025 set_cont_sequence (char *new_seq)
19026 {
19027     int len;
19028     Boolean ret;
19029
19030     // handle bad attempts to set the sequence
19031         if (!new_seq)
19032                 return 0; // acceptable error - no debug
19033
19034     len = strlen(new_seq);
19035     ret = (len > 0) && (len < sizeof(cseq));
19036     if (ret)
19037       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19038     else if (appData.debugMode)
19039       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19040     return ret;
19041 }
19042
19043 /*
19044     reformat a source message so words don't cross the width boundary.  internal
19045     newlines are not removed.  returns the wrapped size (no null character unless
19046     included in source message).  If dest is NULL, only calculate the size required
19047     for the dest buffer.  lp argument indicats line position upon entry, and it's
19048     passed back upon exit.
19049 */
19050 int
19051 wrap (char *dest, char *src, int count, int width, int *lp)
19052 {
19053     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19054
19055     cseq_len = strlen(cseq);
19056     old_line = line = *lp;
19057     ansi = len = clen = 0;
19058
19059     for (i=0; i < count; i++)
19060     {
19061         if (src[i] == '\033')
19062             ansi = 1;
19063
19064         // if we hit the width, back up
19065         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19066         {
19067             // store i & len in case the word is too long
19068             old_i = i, old_len = len;
19069
19070             // find the end of the last word
19071             while (i && src[i] != ' ' && src[i] != '\n')
19072             {
19073                 i--;
19074                 len--;
19075             }
19076
19077             // word too long?  restore i & len before splitting it
19078             if ((old_i-i+clen) >= width)
19079             {
19080                 i = old_i;
19081                 len = old_len;
19082             }
19083
19084             // extra space?
19085             if (i && src[i-1] == ' ')
19086                 len--;
19087
19088             if (src[i] != ' ' && src[i] != '\n')
19089             {
19090                 i--;
19091                 if (len)
19092                     len--;
19093             }
19094
19095             // now append the newline and continuation sequence
19096             if (dest)
19097                 dest[len] = '\n';
19098             len++;
19099             if (dest)
19100                 strncpy(dest+len, cseq, cseq_len);
19101             len += cseq_len;
19102             line = cseq_len;
19103             clen = cseq_len;
19104             continue;
19105         }
19106
19107         if (dest)
19108             dest[len] = src[i];
19109         len++;
19110         if (!ansi)
19111             line++;
19112         if (src[i] == '\n')
19113             line = 0;
19114         if (src[i] == 'm')
19115             ansi = 0;
19116     }
19117     if (dest && appData.debugMode)
19118     {
19119         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19120             count, width, line, len, *lp);
19121         show_bytes(debugFP, src, count);
19122         fprintf(debugFP, "\ndest: ");
19123         show_bytes(debugFP, dest, len);
19124         fprintf(debugFP, "\n");
19125     }
19126     *lp = dest ? line : old_line;
19127
19128     return len;
19129 }
19130
19131 // [HGM] vari: routines for shelving variations
19132 Boolean modeRestore = FALSE;
19133
19134 void
19135 PushInner (int firstMove, int lastMove)
19136 {
19137         int i, j, nrMoves = lastMove - firstMove;
19138
19139         // push current tail of game on stack
19140         savedResult[storedGames] = gameInfo.result;
19141         savedDetails[storedGames] = gameInfo.resultDetails;
19142         gameInfo.resultDetails = NULL;
19143         savedFirst[storedGames] = firstMove;
19144         savedLast [storedGames] = lastMove;
19145         savedFramePtr[storedGames] = framePtr;
19146         framePtr -= nrMoves; // reserve space for the boards
19147         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19148             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19149             for(j=0; j<MOVE_LEN; j++)
19150                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19151             for(j=0; j<2*MOVE_LEN; j++)
19152                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19153             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19154             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19155             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19156             pvInfoList[firstMove+i-1].depth = 0;
19157             commentList[framePtr+i] = commentList[firstMove+i];
19158             commentList[firstMove+i] = NULL;
19159         }
19160
19161         storedGames++;
19162         forwardMostMove = firstMove; // truncate game so we can start variation
19163 }
19164
19165 void
19166 PushTail (int firstMove, int lastMove)
19167 {
19168         if(appData.icsActive) { // only in local mode
19169                 forwardMostMove = currentMove; // mimic old ICS behavior
19170                 return;
19171         }
19172         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19173
19174         PushInner(firstMove, lastMove);
19175         if(storedGames == 1) GreyRevert(FALSE);
19176         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19177 }
19178
19179 void
19180 PopInner (Boolean annotate)
19181 {
19182         int i, j, nrMoves;
19183         char buf[8000], moveBuf[20];
19184
19185         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19186         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19187         nrMoves = savedLast[storedGames] - currentMove;
19188         if(annotate) {
19189                 int cnt = 10;
19190                 if(!WhiteOnMove(currentMove))
19191                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19192                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19193                 for(i=currentMove; i<forwardMostMove; i++) {
19194                         if(WhiteOnMove(i))
19195                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19196                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19197                         strcat(buf, moveBuf);
19198                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19199                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19200                 }
19201                 strcat(buf, ")");
19202         }
19203         for(i=1; i<=nrMoves; i++) { // copy last variation back
19204             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19205             for(j=0; j<MOVE_LEN; j++)
19206                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19207             for(j=0; j<2*MOVE_LEN; j++)
19208                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19209             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19210             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19211             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19212             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19213             commentList[currentMove+i] = commentList[framePtr+i];
19214             commentList[framePtr+i] = NULL;
19215         }
19216         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19217         framePtr = savedFramePtr[storedGames];
19218         gameInfo.result = savedResult[storedGames];
19219         if(gameInfo.resultDetails != NULL) {
19220             free(gameInfo.resultDetails);
19221       }
19222         gameInfo.resultDetails = savedDetails[storedGames];
19223         forwardMostMove = currentMove + nrMoves;
19224 }
19225
19226 Boolean
19227 PopTail (Boolean annotate)
19228 {
19229         if(appData.icsActive) return FALSE; // only in local mode
19230         if(!storedGames) return FALSE; // sanity
19231         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19232
19233         PopInner(annotate);
19234         if(currentMove < forwardMostMove) ForwardEvent(); else
19235         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19236
19237         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19238         return TRUE;
19239 }
19240
19241 void
19242 CleanupTail ()
19243 {       // remove all shelved variations
19244         int i;
19245         for(i=0; i<storedGames; i++) {
19246             if(savedDetails[i])
19247                 free(savedDetails[i]);
19248             savedDetails[i] = NULL;
19249         }
19250         for(i=framePtr; i<MAX_MOVES; i++) {
19251                 if(commentList[i]) free(commentList[i]);
19252                 commentList[i] = NULL;
19253         }
19254         framePtr = MAX_MOVES-1;
19255         storedGames = 0;
19256 }
19257
19258 void
19259 LoadVariation (int index, char *text)
19260 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19261         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19262         int level = 0, move;
19263
19264         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19265         // first find outermost bracketing variation
19266         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19267             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19268                 if(*p == '{') wait = '}'; else
19269                 if(*p == '[') wait = ']'; else
19270                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19271                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19272             }
19273             if(*p == wait) wait = NULLCHAR; // closing ]} found
19274             p++;
19275         }
19276         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19277         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19278         end[1] = NULLCHAR; // clip off comment beyond variation
19279         ToNrEvent(currentMove-1);
19280         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19281         // kludge: use ParsePV() to append variation to game
19282         move = currentMove;
19283         ParsePV(start, TRUE, TRUE);
19284         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19285         ClearPremoveHighlights();
19286         CommentPopDown();
19287         ToNrEvent(currentMove+1);
19288 }
19289
19290 int transparency[2];
19291
19292 void
19293 LoadTheme ()
19294 {
19295 #define BUF_SIZ (2*MSG_SIZ)
19296     char *p, *q, buf[BUF_SIZ];
19297     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19298         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19299         ParseArgsFromString(buf);
19300         ActivateTheme(TRUE); // also redo colors
19301         return;
19302     }
19303     p = nickName;
19304     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19305     {
19306         int len;
19307         q = appData.themeNames;
19308         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19309       if(appData.useBitmaps) {
19310         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19311                 Shorten(appData.liteBackTextureFile));
19312         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19313                 Shorten(appData.darkBackTextureFile),
19314                 appData.liteBackTextureMode,
19315                 appData.darkBackTextureMode );
19316       } else {
19317         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19318       }
19319       if(!appData.useBitmaps || transparency[0]) {
19320         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19321       }
19322       if(!appData.useBitmaps || transparency[1]) {
19323         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19324       }
19325       if(appData.useBorder) {
19326         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19327                 appData.border);
19328       } else {
19329         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19330       }
19331       if(appData.useFont) {
19332         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19333                 appData.renderPiecesWithFont,
19334                 appData.fontToPieceTable,
19335                 Col2Text(9),    // appData.fontBackColorWhite
19336                 Col2Text(10) ); // appData.fontForeColorBlack
19337       } else {
19338         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19339         if(appData.pieceDirectory[0]) {
19340           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19341           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19342             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19343         }
19344         if(!appData.pieceDirectory[0] || !appData.trueColors)
19345           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19346                 Col2Text(0),   // whitePieceColor
19347                 Col2Text(1) ); // blackPieceColor
19348       }
19349       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19350                 Col2Text(4),   // highlightSquareColor
19351                 Col2Text(5) ); // premoveHighlightColor
19352         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19353         if(insert != q) insert[-1] = NULLCHAR;
19354         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19355         if(q)   free(q);
19356     }
19357     ActivateTheme(FALSE);
19358 }