Use flexible promotion assignment
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 signed char  initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                       }
4174                     }
4175
4176                     /* Usually suppress following prompt */
4177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4178                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4179                         if (looking_at(buf, &i, "*% ")) {
4180                             savingComment = FALSE;
4181                             suppressKibitz = 0;
4182                         }
4183                     }
4184                     next_out = i;
4185                 } else if (started == STARTED_HOLDINGS) {
4186                     int gamenum;
4187                     char new_piece[MSG_SIZ];
4188                     started = STARTED_NONE;
4189                     parse[parse_pos] = NULLCHAR;
4190                     if (appData.debugMode)
4191                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4192                                                         parse, currentMove);
4193                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4194                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4195                         if (gameInfo.variant == VariantNormal) {
4196                           /* [HGM] We seem to switch variant during a game!
4197                            * Presumably no holdings were displayed, so we have
4198                            * to move the position two files to the right to
4199                            * create room for them!
4200                            */
4201                           VariantClass newVariant;
4202                           switch(gameInfo.boardWidth) { // base guess on board width
4203                                 case 9:  newVariant = VariantShogi; break;
4204                                 case 10: newVariant = VariantGreat; break;
4205                                 default: newVariant = VariantCrazyhouse; break;
4206                           }
4207                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4208                           /* Get a move list just to see the header, which
4209                              will tell us whether this is really bug or zh */
4210                           if (ics_getting_history == H_FALSE) {
4211                             ics_getting_history = H_REQUESTED;
4212                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4213                             SendToICS(str);
4214                           }
4215                         }
4216                         new_piece[0] = NULLCHAR;
4217                         sscanf(parse, "game %d white [%s black [%s <- %s",
4218                                &gamenum, white_holding, black_holding,
4219                                new_piece);
4220                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4221                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4222                         /* [HGM] copy holdings to board holdings area */
4223                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4224                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4225                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4226 #if ZIPPY
4227                         if (appData.zippyPlay && first.initDone) {
4228                             ZippyHoldings(white_holding, black_holding,
4229                                           new_piece);
4230                         }
4231 #endif /*ZIPPY*/
4232                         if (tinyLayout || smallLayout) {
4233                             char wh[16], bh[16];
4234                             PackHolding(wh, white_holding);
4235                             PackHolding(bh, black_holding);
4236                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4237                                     gameInfo.white, gameInfo.black);
4238                         } else {
4239                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4240                                     gameInfo.white, white_holding, _("vs."),
4241                                     gameInfo.black, black_holding);
4242                         }
4243                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4244                         DrawPosition(FALSE, boards[currentMove]);
4245                         DisplayTitle(str);
4246                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4247                         sscanf(parse, "game %d white [%s black [%s <- %s",
4248                                &gamenum, white_holding, black_holding,
4249                                new_piece);
4250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4252                         /* [HGM] copy holdings to partner-board holdings area */
4253                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4254                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4255                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4256                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4257                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4258                       }
4259                     }
4260                     /* Suppress following prompt */
4261                     if (looking_at(buf, &i, "*% ")) {
4262                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4263                         savingComment = FALSE;
4264                         suppressKibitz = 0;
4265                     }
4266                     next_out = i;
4267                 }
4268                 continue;
4269             }
4270
4271             i++;                /* skip unparsed character and loop back */
4272         }
4273
4274         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4275 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4276 //          SendToPlayer(&buf[next_out], i - next_out);
4277             started != STARTED_HOLDINGS && leftover_start > next_out) {
4278             SendToPlayer(&buf[next_out], leftover_start - next_out);
4279             next_out = i;
4280         }
4281
4282         leftover_len = buf_len - leftover_start;
4283         /* if buffer ends with something we couldn't parse,
4284            reparse it after appending the next read */
4285
4286     } else if (count == 0) {
4287         RemoveInputSource(isr);
4288         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4289     } else {
4290         DisplayFatalError(_("Error reading from ICS"), error, 1);
4291     }
4292 }
4293
4294
4295 /* Board style 12 looks like this:
4296
4297    <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
4298
4299  * The "<12> " is stripped before it gets to this routine.  The two
4300  * trailing 0's (flip state and clock ticking) are later addition, and
4301  * some chess servers may not have them, or may have only the first.
4302  * Additional trailing fields may be added in the future.
4303  */
4304
4305 #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"
4306
4307 #define RELATION_OBSERVING_PLAYED    0
4308 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4309 #define RELATION_PLAYING_MYMOVE      1
4310 #define RELATION_PLAYING_NOTMYMOVE  -1
4311 #define RELATION_EXAMINING           2
4312 #define RELATION_ISOLATED_BOARD     -3
4313 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4314
4315 void
4316 ParseBoard12 (char *string)
4317 {
4318 #if ZIPPY
4319     int i, takeback;
4320     char *bookHit = NULL; // [HGM] book
4321 #endif
4322     GameMode newGameMode;
4323     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4324     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4325     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4326     char to_play, board_chars[200];
4327     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4328     char black[32], white[32];
4329     Board board;
4330     int prevMove = currentMove;
4331     int ticking = 2;
4332     ChessMove moveType;
4333     int fromX, fromY, toX, toY;
4334     char promoChar;
4335     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4336     Boolean weird = FALSE, reqFlag = FALSE;
4337
4338     fromX = fromY = toX = toY = -1;
4339
4340     newGame = FALSE;
4341
4342     if (appData.debugMode)
4343       fprintf(debugFP, "Parsing board: %s\n", string);
4344
4345     move_str[0] = NULLCHAR;
4346     elapsed_time[0] = NULLCHAR;
4347     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4348         int  i = 0, j;
4349         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4350             if(string[i] == ' ') { ranks++; files = 0; }
4351             else files++;
4352             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4353             i++;
4354         }
4355         for(j = 0; j <i; j++) board_chars[j] = string[j];
4356         board_chars[i] = '\0';
4357         string += i + 1;
4358     }
4359     n = sscanf(string, PATTERN, &to_play, &double_push,
4360                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4361                &gamenum, white, black, &relation, &basetime, &increment,
4362                &white_stren, &black_stren, &white_time, &black_time,
4363                &moveNum, str, elapsed_time, move_str, &ics_flip,
4364                &ticking);
4365
4366     if (n < 21) {
4367         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4368         DisplayError(str, 0);
4369         return;
4370     }
4371
4372     /* Convert the move number to internal form */
4373     moveNum = (moveNum - 1) * 2;
4374     if (to_play == 'B') moveNum++;
4375     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4376       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4377                         0, 1);
4378       return;
4379     }
4380
4381     switch (relation) {
4382       case RELATION_OBSERVING_PLAYED:
4383       case RELATION_OBSERVING_STATIC:
4384         if (gamenum == -1) {
4385             /* Old ICC buglet */
4386             relation = RELATION_OBSERVING_STATIC;
4387         }
4388         newGameMode = IcsObserving;
4389         break;
4390       case RELATION_PLAYING_MYMOVE:
4391       case RELATION_PLAYING_NOTMYMOVE:
4392         newGameMode =
4393           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4394             IcsPlayingWhite : IcsPlayingBlack;
4395         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4396         break;
4397       case RELATION_EXAMINING:
4398         newGameMode = IcsExamining;
4399         break;
4400       case RELATION_ISOLATED_BOARD:
4401       default:
4402         /* Just display this board.  If user was doing something else,
4403            we will forget about it until the next board comes. */
4404         newGameMode = IcsIdle;
4405         break;
4406       case RELATION_STARTING_POSITION:
4407         newGameMode = gameMode;
4408         break;
4409     }
4410
4411     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4412         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4413          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4414       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4415       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4416       static int lastBgGame = -1;
4417       char *toSqr;
4418       for (k = 0; k < ranks; k++) {
4419         for (j = 0; j < files; j++)
4420           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4421         if(gameInfo.holdingsWidth > 1) {
4422              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4423              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4424         }
4425       }
4426       CopyBoard(partnerBoard, board);
4427       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4428         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4429         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4431       if(toSqr = strchr(str, '-')) {
4432         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4433         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4434       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4435       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4436       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4437       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4438       if(twoBoards) {
4439           DisplayWhiteClock(white_time*fac, to_play == 'W');
4440           DisplayBlackClock(black_time*fac, to_play != 'W');
4441           activePartner = to_play;
4442           if(gamenum != lastBgGame) {
4443               char buf[MSG_SIZ];
4444               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4445               DisplayTitle(buf);
4446           }
4447           lastBgGame = gamenum;
4448           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4449                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4450       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4451                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4452       if(!twoBoards) DisplayMessage(partnerStatus, "");
4453         partnerBoardValid = TRUE;
4454       return;
4455     }
4456
4457     if(appData.dualBoard && appData.bgObserve) {
4458         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4459             SendToICS(ics_prefix), SendToICS("pobserve\n");
4460         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4461             char buf[MSG_SIZ];
4462             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4463             SendToICS(buf);
4464         }
4465     }
4466
4467     /* Modify behavior for initial board display on move listing
4468        of wild games.
4469        */
4470     switch (ics_getting_history) {
4471       case H_FALSE:
4472       case H_REQUESTED:
4473         break;
4474       case H_GOT_REQ_HEADER:
4475       case H_GOT_UNREQ_HEADER:
4476         /* This is the initial position of the current game */
4477         gamenum = ics_gamenum;
4478         moveNum = 0;            /* old ICS bug workaround */
4479         if (to_play == 'B') {
4480           startedFromSetupPosition = TRUE;
4481           blackPlaysFirst = TRUE;
4482           moveNum = 1;
4483           if (forwardMostMove == 0) forwardMostMove = 1;
4484           if (backwardMostMove == 0) backwardMostMove = 1;
4485           if (currentMove == 0) currentMove = 1;
4486         }
4487         newGameMode = gameMode;
4488         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4489         break;
4490       case H_GOT_UNWANTED_HEADER:
4491         /* This is an initial board that we don't want */
4492         return;
4493       case H_GETTING_MOVES:
4494         /* Should not happen */
4495         DisplayError(_("Error gathering move list: extra board"), 0);
4496         ics_getting_history = H_FALSE;
4497         return;
4498     }
4499
4500    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4501                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4502                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4503      /* [HGM] We seem to have switched variant unexpectedly
4504       * Try to guess new variant from board size
4505       */
4506           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4507           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4508           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4509           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4510           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4511           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4512           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4513           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4514           /* Get a move list just to see the header, which
4515              will tell us whether this is really bug or zh */
4516           if (ics_getting_history == H_FALSE) {
4517             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4518             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4519             SendToICS(str);
4520           }
4521     }
4522
4523     /* Take action if this is the first board of a new game, or of a
4524        different game than is currently being displayed.  */
4525     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4526         relation == RELATION_ISOLATED_BOARD) {
4527
4528         /* Forget the old game and get the history (if any) of the new one */
4529         if (gameMode != BeginningOfGame) {
4530           Reset(TRUE, TRUE);
4531         }
4532         newGame = TRUE;
4533         if (appData.autoRaiseBoard) BoardToTop();
4534         prevMove = -3;
4535         if (gamenum == -1) {
4536             newGameMode = IcsIdle;
4537         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4538                    appData.getMoveList && !reqFlag) {
4539             /* Need to get game history */
4540             ics_getting_history = H_REQUESTED;
4541             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542             SendToICS(str);
4543         }
4544
4545         /* Initially flip the board to have black on the bottom if playing
4546            black or if the ICS flip flag is set, but let the user change
4547            it with the Flip View button. */
4548         flipView = appData.autoFlipView ?
4549           (newGameMode == IcsPlayingBlack) || ics_flip :
4550           appData.flipView;
4551
4552         /* Done with values from previous mode; copy in new ones */
4553         gameMode = newGameMode;
4554         ModeHighlight();
4555         ics_gamenum = gamenum;
4556         if (gamenum == gs_gamenum) {
4557             int klen = strlen(gs_kind);
4558             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4559             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4560             gameInfo.event = StrSave(str);
4561         } else {
4562             gameInfo.event = StrSave("ICS game");
4563         }
4564         gameInfo.site = StrSave(appData.icsHost);
4565         gameInfo.date = PGNDate();
4566         gameInfo.round = StrSave("-");
4567         gameInfo.white = StrSave(white);
4568         gameInfo.black = StrSave(black);
4569         timeControl = basetime * 60 * 1000;
4570         timeControl_2 = 0;
4571         timeIncrement = increment * 1000;
4572         movesPerSession = 0;
4573         gameInfo.timeControl = TimeControlTagValue();
4574         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4575   if (appData.debugMode) {
4576     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4577     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4578     setbuf(debugFP, NULL);
4579   }
4580
4581         gameInfo.outOfBook = NULL;
4582
4583         /* Do we have the ratings? */
4584         if (strcmp(player1Name, white) == 0 &&
4585             strcmp(player2Name, black) == 0) {
4586             if (appData.debugMode)
4587               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4588                       player1Rating, player2Rating);
4589             gameInfo.whiteRating = player1Rating;
4590             gameInfo.blackRating = player2Rating;
4591         } else if (strcmp(player2Name, white) == 0 &&
4592                    strcmp(player1Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player2Rating, player1Rating);
4596             gameInfo.whiteRating = player2Rating;
4597             gameInfo.blackRating = player1Rating;
4598         }
4599         player1Name[0] = player2Name[0] = NULLCHAR;
4600
4601         /* Silence shouts if requested */
4602         if (appData.quietPlay &&
4603             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4604             SendToICS(ics_prefix);
4605             SendToICS("set shout 0\n");
4606         }
4607     }
4608
4609     /* Deal with midgame name changes */
4610     if (!newGame) {
4611         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4612             if (gameInfo.white) free(gameInfo.white);
4613             gameInfo.white = StrSave(white);
4614         }
4615         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4616             if (gameInfo.black) free(gameInfo.black);
4617             gameInfo.black = StrSave(black);
4618         }
4619     }
4620
4621     /* Throw away game result if anything actually changes in examine mode */
4622     if (gameMode == IcsExamining && !newGame) {
4623         gameInfo.result = GameUnfinished;
4624         if (gameInfo.resultDetails != NULL) {
4625             free(gameInfo.resultDetails);
4626             gameInfo.resultDetails = NULL;
4627         }
4628     }
4629
4630     /* In pausing && IcsExamining mode, we ignore boards coming
4631        in if they are in a different variation than we are. */
4632     if (pauseExamInvalid) return;
4633     if (pausing && gameMode == IcsExamining) {
4634         if (moveNum <= pauseExamForwardMostMove) {
4635             pauseExamInvalid = TRUE;
4636             forwardMostMove = pauseExamForwardMostMove;
4637             return;
4638         }
4639     }
4640
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4643   }
4644     /* Parse the board */
4645     for (k = 0; k < ranks; k++) {
4646       for (j = 0; j < files; j++)
4647         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4648       if(gameInfo.holdingsWidth > 1) {
4649            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4650            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4651       }
4652     }
4653     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4654       board[5][BOARD_RGHT+1] = WhiteAngel;
4655       board[6][BOARD_RGHT+1] = WhiteMarshall;
4656       board[1][0] = BlackMarshall;
4657       board[2][0] = BlackAngel;
4658       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4659     }
4660     CopyBoard(boards[moveNum], board);
4661     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4662     if (moveNum == 0) {
4663         startedFromSetupPosition =
4664           !CompareBoards(board, initialPosition);
4665         if(startedFromSetupPosition)
4666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4667     }
4668
4669     /* [HGM] Set castling rights. Take the outermost Rooks,
4670        to make it also work for FRC opening positions. Note that board12
4671        is really defective for later FRC positions, as it has no way to
4672        indicate which Rook can castle if they are on the same side of King.
4673        For the initial position we grant rights to the outermost Rooks,
4674        and remember thos rights, and we then copy them on positions
4675        later in an FRC game. This means WB might not recognize castlings with
4676        Rooks that have moved back to their original position as illegal,
4677        but in ICS mode that is not its job anyway.
4678     */
4679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4681
4682         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4683             if(board[0][i] == WhiteRook) j = i;
4684         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4686             if(board[0][i] == WhiteRook) j = i;
4687         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4688         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4690         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4691         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4693         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4694
4695         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4696         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4699         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4700             if(board[BOARD_HEIGHT-1][k] == bKing)
4701                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4702         if(gameInfo.variant == VariantTwoKings) {
4703             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4704             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4705             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4706         }
4707     } else { int r;
4708         r = boards[moveNum][CASTLING][0] = initialRights[0];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4710         r = boards[moveNum][CASTLING][1] = initialRights[1];
4711         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4712         r = boards[moveNum][CASTLING][3] = initialRights[3];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4714         r = boards[moveNum][CASTLING][4] = initialRights[4];
4715         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4716         /* wildcastle kludge: always assume King has rights */
4717         r = boards[moveNum][CASTLING][2] = initialRights[2];
4718         r = boards[moveNum][CASTLING][5] = initialRights[5];
4719     }
4720     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4721     boards[moveNum][EP_STATUS] = EP_NONE;
4722     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4723     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4724     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4725
4726
4727     if (ics_getting_history == H_GOT_REQ_HEADER ||
4728         ics_getting_history == H_GOT_UNREQ_HEADER) {
4729         /* This was an initial position from a move list, not
4730            the current position */
4731         return;
4732     }
4733
4734     /* Update currentMove and known move number limits */
4735     newMove = newGame || moveNum > forwardMostMove;
4736
4737     if (newGame) {
4738         forwardMostMove = backwardMostMove = currentMove = moveNum;
4739         if (gameMode == IcsExamining && moveNum == 0) {
4740           /* Workaround for ICS limitation: we are not told the wild
4741              type when starting to examine a game.  But if we ask for
4742              the move list, the move list header will tell us */
4743             ics_getting_history = H_REQUESTED;
4744             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4745             SendToICS(str);
4746         }
4747     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4748                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4749 #if ZIPPY
4750         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4751         /* [HGM] applied this also to an engine that is silently watching        */
4752         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4753             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4754             gameInfo.variant == currentlyInitializedVariant) {
4755           takeback = forwardMostMove - moveNum;
4756           for (i = 0; i < takeback; i++) {
4757             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4758             SendToProgram("undo\n", &first);
4759           }
4760         }
4761 #endif
4762
4763         forwardMostMove = moveNum;
4764         if (!pausing || currentMove > forwardMostMove)
4765           currentMove = forwardMostMove;
4766     } else {
4767         /* New part of history that is not contiguous with old part */
4768         if (pausing && gameMode == IcsExamining) {
4769             pauseExamInvalid = TRUE;
4770             forwardMostMove = pauseExamForwardMostMove;
4771             return;
4772         }
4773         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4774 #if ZIPPY
4775             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4776                 // [HGM] when we will receive the move list we now request, it will be
4777                 // fed to the engine from the first move on. So if the engine is not
4778                 // in the initial position now, bring it there.
4779                 InitChessProgram(&first, 0);
4780             }
4781 #endif
4782             ics_getting_history = H_REQUESTED;
4783             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4784             SendToICS(str);
4785         }
4786         forwardMostMove = backwardMostMove = currentMove = moveNum;
4787     }
4788
4789     /* Update the clocks */
4790     if (strchr(elapsed_time, '.')) {
4791       /* Time is in ms */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4794     } else {
4795       /* Time is in seconds */
4796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4798     }
4799
4800
4801 #if ZIPPY
4802     if (appData.zippyPlay && newGame &&
4803         gameMode != IcsObserving && gameMode != IcsIdle &&
4804         gameMode != IcsExamining)
4805       ZippyFirstBoard(moveNum, basetime, increment);
4806 #endif
4807
4808     /* Put the move on the move list, first converting
4809        to canonical algebraic form. */
4810     if (moveNum > 0) {
4811   if (appData.debugMode) {
4812     int f = forwardMostMove;
4813     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4814             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4815             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4816     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4817     fprintf(debugFP, "moveNum = %d\n", moveNum);
4818     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4819     setbuf(debugFP, NULL);
4820   }
4821         if (moveNum <= backwardMostMove) {
4822             /* We don't know what the board looked like before
4823                this move.  Punt. */
4824           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4825             strcat(parseList[moveNum - 1], " ");
4826             strcat(parseList[moveNum - 1], elapsed_time);
4827             moveList[moveNum - 1][0] = NULLCHAR;
4828         } else if (strcmp(move_str, "none") == 0) {
4829             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4830             /* Again, we don't know what the board looked like;
4831                this is really the start of the game. */
4832             parseList[moveNum - 1][0] = NULLCHAR;
4833             moveList[moveNum - 1][0] = NULLCHAR;
4834             backwardMostMove = moveNum;
4835             startedFromSetupPosition = TRUE;
4836             fromX = fromY = toX = toY = -1;
4837         } else {
4838           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4839           //                 So we parse the long-algebraic move string in stead of the SAN move
4840           int valid; char buf[MSG_SIZ], *prom;
4841
4842           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4843                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4844           // str looks something like "Q/a1-a2"; kill the slash
4845           if(str[1] == '/')
4846             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4847           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4848           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4849                 strcat(buf, prom); // long move lacks promo specification!
4850           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4851                 if(appData.debugMode)
4852                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4853                 safeStrCpy(move_str, buf, MSG_SIZ);
4854           }
4855           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4856                                 &fromX, &fromY, &toX, &toY, &promoChar)
4857                || ParseOneMove(buf, moveNum - 1, &moveType,
4858                                 &fromX, &fromY, &toX, &toY, &promoChar);
4859           // end of long SAN patch
4860           if (valid) {
4861             (void) CoordsToAlgebraic(boards[moveNum - 1],
4862                                      PosFlags(moveNum - 1),
4863                                      fromY, fromX, toY, toX, promoChar,
4864                                      parseList[moveNum-1]);
4865             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4866               case MT_NONE:
4867               case MT_STALEMATE:
4868               default:
4869                 break;
4870               case MT_CHECK:
4871                 if(!IS_SHOGI(gameInfo.variant))
4872                     strcat(parseList[moveNum - 1], "+");
4873                 break;
4874               case MT_CHECKMATE:
4875               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4876                 strcat(parseList[moveNum - 1], "#");
4877                 break;
4878             }
4879             strcat(parseList[moveNum - 1], " ");
4880             strcat(parseList[moveNum - 1], elapsed_time);
4881             /* currentMoveString is set as a side-effect of ParseOneMove */
4882             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4883             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4884             strcat(moveList[moveNum - 1], "\n");
4885
4886             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4887                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4888               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4889                 ChessSquare old, new = boards[moveNum][k][j];
4890                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4891                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4892                   if(old == new) continue;
4893                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4894                   else if(new == WhiteWazir || new == BlackWazir) {
4895                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4896                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4897                       else boards[moveNum][k][j] = old; // preserve type of Gold
4898                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4899                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4900               }
4901           } else {
4902             /* Move from ICS was illegal!?  Punt. */
4903             if (appData.debugMode) {
4904               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4905               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4906             }
4907             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4908             strcat(parseList[moveNum - 1], " ");
4909             strcat(parseList[moveNum - 1], elapsed_time);
4910             moveList[moveNum - 1][0] = NULLCHAR;
4911             fromX = fromY = toX = toY = -1;
4912           }
4913         }
4914   if (appData.debugMode) {
4915     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4916     setbuf(debugFP, NULL);
4917   }
4918
4919 #if ZIPPY
4920         /* Send move to chess program (BEFORE animating it). */
4921         if (appData.zippyPlay && !newGame && newMove &&
4922            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4923
4924             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4925                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4926                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4927                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4928                             move_str);
4929                     DisplayError(str, 0);
4930                 } else {
4931                     if (first.sendTime) {
4932                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4933                     }
4934                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4935                     if (firstMove && !bookHit) {
4936                         firstMove = FALSE;
4937                         if (first.useColors) {
4938                           SendToProgram(gameMode == IcsPlayingWhite ?
4939                                         "white\ngo\n" :
4940                                         "black\ngo\n", &first);
4941                         } else {
4942                           SendToProgram("go\n", &first);
4943                         }
4944                         first.maybeThinking = TRUE;
4945                     }
4946                 }
4947             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4948               if (moveList[moveNum - 1][0] == NULLCHAR) {
4949                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4950                 DisplayError(str, 0);
4951               } else {
4952                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4953                 SendMoveToProgram(moveNum - 1, &first);
4954               }
4955             }
4956         }
4957 #endif
4958     }
4959
4960     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4961         /* If move comes from a remote source, animate it.  If it
4962            isn't remote, it will have already been animated. */
4963         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4964             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4965         }
4966         if (!pausing && appData.highlightLastMove) {
4967             SetHighlights(fromX, fromY, toX, toY);
4968         }
4969     }
4970
4971     /* Start the clocks */
4972     whiteFlag = blackFlag = FALSE;
4973     appData.clockMode = !(basetime == 0 && increment == 0);
4974     if (ticking == 0) {
4975       ics_clock_paused = TRUE;
4976       StopClocks();
4977     } else if (ticking == 1) {
4978       ics_clock_paused = FALSE;
4979     }
4980     if (gameMode == IcsIdle ||
4981         relation == RELATION_OBSERVING_STATIC ||
4982         relation == RELATION_EXAMINING ||
4983         ics_clock_paused)
4984       DisplayBothClocks();
4985     else
4986       StartClocks();
4987
4988     /* Display opponents and material strengths */
4989     if (gameInfo.variant != VariantBughouse &&
4990         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4991         if (tinyLayout || smallLayout) {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4998                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4999                     basetime, increment, (int) gameInfo.variant);
5000         } else {
5001             if(gameInfo.variant == VariantNormal)
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment);
5005             else
5006               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5007                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5008                     basetime, increment, VariantName(gameInfo.variant));
5009         }
5010         DisplayTitle(str);
5011   if (appData.debugMode) {
5012     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5013   }
5014     }
5015
5016
5017     /* Display the board */
5018     if (!pausing && !appData.noGUI) {
5019
5020       if (appData.premove)
5021           if (!gotPremove ||
5022              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5023              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5024               ClearPremoveHighlights();
5025
5026       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5027         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5028       DrawPosition(j, boards[currentMove]);
5029
5030       DisplayMove(moveNum - 1);
5031       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5032             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5033               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5034         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5035       }
5036     }
5037
5038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5039 #if ZIPPY
5040     if(bookHit) { // [HGM] book: simulate book reply
5041         static char bookMove[MSG_SIZ]; // a bit generous?
5042
5043         programStats.nodes = programStats.depth = programStats.time =
5044         programStats.score = programStats.got_only_move = 0;
5045         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5046
5047         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5048         strcat(bookMove, bookHit);
5049         HandleMachineMove(bookMove, &first);
5050     }
5051 #endif
5052 }
5053
5054 void
5055 GetMoveListEvent ()
5056 {
5057     char buf[MSG_SIZ];
5058     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5059         ics_getting_history = H_REQUESTED;
5060         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5061         SendToICS(buf);
5062     }
5063 }
5064
5065 void
5066 SendToBoth (char *msg)
5067 {   // to make it easy to keep two engines in step in dual analysis
5068     SendToProgram(msg, &first);
5069     if(second.analyzing) SendToProgram(msg, &second);
5070 }
5071
5072 void
5073 AnalysisPeriodicEvent (int force)
5074 {
5075     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5076          && !force) || !appData.periodicUpdates)
5077       return;
5078
5079     /* Send . command to Crafty to collect stats */
5080     SendToBoth(".\n");
5081
5082     /* Don't send another until we get a response (this makes
5083        us stop sending to old Crafty's which don't understand
5084        the "." command (sending illegal cmds resets node count & time,
5085        which looks bad)) */
5086     programStats.ok_to_send = 0;
5087 }
5088
5089 void
5090 ics_update_width (int new_width)
5091 {
5092         ics_printf("set width %d\n", new_width);
5093 }
5094
5095 void
5096 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5097 {
5098     char buf[MSG_SIZ];
5099
5100     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5101         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5102             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5103             SendToProgram(buf, cps);
5104             return;
5105         }
5106         // null move in variant where engine does not understand it (for analysis purposes)
5107         SendBoard(cps, moveNum + 1); // send position after move in stead.
5108         return;
5109     }
5110     if (cps->useUsermove) {
5111       SendToProgram("usermove ", cps);
5112     }
5113     if (cps->useSAN) {
5114       char *space;
5115       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5116         int len = space - parseList[moveNum];
5117         memcpy(buf, parseList[moveNum], len);
5118         buf[len++] = '\n';
5119         buf[len] = NULLCHAR;
5120       } else {
5121         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5122       }
5123       SendToProgram(buf, cps);
5124     } else {
5125       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5126         AlphaRank(moveList[moveNum], 4);
5127         SendToProgram(moveList[moveNum], cps);
5128         AlphaRank(moveList[moveNum], 4); // and back
5129       } else
5130       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5131        * the engine. It would be nice to have a better way to identify castle
5132        * moves here. */
5133       if(appData.fischerCastling && cps->useOOCastle) {
5134         int fromX = moveList[moveNum][0] - AAA;
5135         int fromY = moveList[moveNum][1] - ONE;
5136         int toX = moveList[moveNum][2] - AAA;
5137         int toY = moveList[moveNum][3] - ONE;
5138         if((boards[moveNum][fromY][fromX] == WhiteKing
5139             && boards[moveNum][toY][toX] == WhiteRook)
5140            || (boards[moveNum][fromY][fromX] == BlackKing
5141                && boards[moveNum][toY][toX] == BlackRook)) {
5142           if(toX > fromX) SendToProgram("O-O\n", cps);
5143           else SendToProgram("O-O-O\n", cps);
5144         }
5145         else SendToProgram(moveList[moveNum], cps);
5146       } else
5147       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5148         char *m = moveList[moveNum];
5149         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
5150           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5151                                                m[2], m[3] - '0',
5152                                                m[5], m[6] - '0',
5153                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5154         else
5155           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5156                                                m[5], m[6] - '0',
5157                                                m[5], m[6] - '0',
5158                                                m[2], m[3] - '0');
5159           SendToProgram(buf, cps);
5160       } else
5161       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5162         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5163           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5164           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5165                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5166         } else
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5168                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5169         SendToProgram(buf, cps);
5170       }
5171       else SendToProgram(moveList[moveNum], cps);
5172       /* End of additions by Tord */
5173     }
5174
5175     /* [HGM] setting up the opening has brought engine in force mode! */
5176     /*       Send 'go' if we are in a mode where machine should play. */
5177     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5178         (gameMode == TwoMachinesPlay   ||
5179 #if ZIPPY
5180          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5181 #endif
5182          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5183         SendToProgram("go\n", cps);
5184   if (appData.debugMode) {
5185     fprintf(debugFP, "(extra)\n");
5186   }
5187     }
5188     setboardSpoiledMachineBlack = 0;
5189 }
5190
5191 void
5192 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5193 {
5194     char user_move[MSG_SIZ];
5195     char suffix[4];
5196
5197     if(gameInfo.variant == VariantSChess && promoChar) {
5198         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5199         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5200     } else suffix[0] = NULLCHAR;
5201
5202     switch (moveType) {
5203       default:
5204         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5205                 (int)moveType, fromX, fromY, toX, toY);
5206         DisplayError(user_move + strlen("say "), 0);
5207         break;
5208       case WhiteKingSideCastle:
5209       case BlackKingSideCastle:
5210       case WhiteQueenSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* PUSH Fabien */
5213       case WhiteHSideCastleFR:
5214       case BlackHSideCastleFR:
5215       /* POP Fabien */
5216         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5217         break;
5218       case WhiteQueenSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case BlackKingSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteASideCastleFR:
5224       case BlackASideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5227         break;
5228       case WhiteNonPromotion:
5229       case BlackNonPromotion:
5230         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5231         break;
5232       case WhitePromotion:
5233       case BlackPromotion:
5234         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5235            gameInfo.variant == VariantMakruk)
5236           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteFerz));
5239         else if(gameInfo.variant == VariantGreat)
5240           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 PieceToChar(WhiteMan));
5243         else
5244           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5246                 promoChar);
5247         break;
5248       case WhiteDrop:
5249       case BlackDrop:
5250       drop:
5251         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5252                  ToUpper(PieceToChar((ChessSquare) fromX)),
5253                  AAA + toX, ONE + toY);
5254         break;
5255       case IllegalMove:  /* could be a variant we don't quite understand */
5256         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5257       case NormalMove:
5258       case WhiteCapturesEnPassant:
5259       case BlackCapturesEnPassant:
5260         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262         break;
5263     }
5264     SendToICS(user_move);
5265     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5266         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5267 }
5268
5269 void
5270 UploadGameEvent ()
5271 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5272     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5273     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5274     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5275       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5276       return;
5277     }
5278     if(gameMode != IcsExamining) { // is this ever not the case?
5279         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5280
5281         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5282           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5283         } else { // on FICS we must first go to general examine mode
5284           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5285         }
5286         if(gameInfo.variant != VariantNormal) {
5287             // try figure out wild number, as xboard names are not always valid on ICS
5288             for(i=1; i<=36; i++) {
5289               snprintf(buf, MSG_SIZ, "wild/%d", i);
5290                 if(StringToVariant(buf) == gameInfo.variant) break;
5291             }
5292             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5293             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5294             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5295         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5296         SendToICS(ics_prefix);
5297         SendToICS(buf);
5298         if(startedFromSetupPosition || backwardMostMove != 0) {
5299           fen = PositionToFEN(backwardMostMove, NULL, 1);
5300           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5301             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5302             SendToICS(buf);
5303           } else { // FICS: everything has to set by separate bsetup commands
5304             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5305             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5306             SendToICS(buf);
5307             if(!WhiteOnMove(backwardMostMove)) {
5308                 SendToICS("bsetup tomove black\n");
5309             }
5310             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5311             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5312             SendToICS(buf);
5313             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5314             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5315             SendToICS(buf);
5316             i = boards[backwardMostMove][EP_STATUS];
5317             if(i >= 0) { // set e.p.
5318               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5319                 SendToICS(buf);
5320             }
5321             bsetup++;
5322           }
5323         }
5324       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5325             SendToICS("bsetup done\n"); // switch to normal examining.
5326     }
5327     for(i = backwardMostMove; i<last; i++) {
5328         char buf[20];
5329         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5330         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5331             int len = strlen(moveList[i]);
5332             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5333             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5334         }
5335         SendToICS(buf);
5336     }
5337     SendToICS(ics_prefix);
5338     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5339 }
5340
5341 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5342 int legNr = 1;
5343
5344 void
5345 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5346 {
5347     if (rf == DROP_RANK) {
5348       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5349       sprintf(move, "%c@%c%c\n",
5350                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5351     } else {
5352         if (promoChar == 'x' || promoChar == NULLCHAR) {
5353           sprintf(move, "%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5355           if(killX >= 0 && killY >= 0) {
5356             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5357             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5358           }
5359         } else {
5360             sprintf(move, "%c%c%c%c%c\n",
5361                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5362         }
5363     }
5364 }
5365
5366 void
5367 ProcessICSInitScript (FILE *f)
5368 {
5369     char buf[MSG_SIZ];
5370
5371     while (fgets(buf, MSG_SIZ, f)) {
5372         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5373     }
5374
5375     fclose(f);
5376 }
5377
5378
5379 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5380 int dragging;
5381 static ClickType lastClickType;
5382
5383 int
5384 Partner (ChessSquare *p)
5385 { // change piece into promotion partner if one shogi-promotes to the other
5386   ChessSquare partner = promoPartner[*p];
5387   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5388   *p = partner;
5389   return 1;
5390 }
5391
5392 void
5393 Sweep (int step)
5394 {
5395     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5396     static int toggleFlag;
5397     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5398     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5399     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5400     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5401     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5402     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5403     do {
5404         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5405         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5406         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5407         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5408         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5409         if(!step) step = -1;
5410     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5411             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5412             promoRestrict[0] ? !strchr(promoRestrict, ToUpper(PieceToChar(promoSweep))) : // if choice set available, use it 
5413             promoSweep == pawn ||
5414             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5415             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5416     if(toX >= 0) {
5417         int victim = boards[currentMove][toY][toX];
5418         boards[currentMove][toY][toX] = promoSweep;
5419         DrawPosition(FALSE, boards[currentMove]);
5420         boards[currentMove][toY][toX] = victim;
5421     } else
5422     ChangeDragPiece(promoSweep);
5423 }
5424
5425 int
5426 PromoScroll (int x, int y)
5427 {
5428   int step = 0;
5429
5430   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5431   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5432   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5433   if(!step) return FALSE;
5434   lastX = x; lastY = y;
5435   if((promoSweep < BlackPawn) == flipView) step = -step;
5436   if(step > 0) selectFlag = 1;
5437   if(!selectFlag) Sweep(step);
5438   return FALSE;
5439 }
5440
5441 void
5442 NextPiece (int step)
5443 {
5444     ChessSquare piece = boards[currentMove][toY][toX];
5445     do {
5446         pieceSweep -= step;
5447         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5448         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5449         if(!step) step = -1;
5450     } while(PieceToChar(pieceSweep) == '.');
5451     boards[currentMove][toY][toX] = pieceSweep;
5452     DrawPosition(FALSE, boards[currentMove]);
5453     boards[currentMove][toY][toX] = piece;
5454 }
5455 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5456 void
5457 AlphaRank (char *move, int n)
5458 {
5459 //    char *p = move, c; int x, y;
5460
5461     if (appData.debugMode) {
5462         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5463     }
5464
5465     if(move[1]=='*' &&
5466        move[2]>='0' && move[2]<='9' &&
5467        move[3]>='a' && move[3]<='x'    ) {
5468         move[1] = '@';
5469         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5470         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5471     } else
5472     if(move[0]>='0' && move[0]<='9' &&
5473        move[1]>='a' && move[1]<='x' &&
5474        move[2]>='0' && move[2]<='9' &&
5475        move[3]>='a' && move[3]<='x'    ) {
5476         /* input move, Shogi -> normal */
5477         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5478         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5479         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5481     } else
5482     if(move[1]=='@' &&
5483        move[3]>='0' && move[3]<='9' &&
5484        move[2]>='a' && move[2]<='x'    ) {
5485         move[1] = '*';
5486         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5487         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5488     } else
5489     if(
5490        move[0]>='a' && move[0]<='x' &&
5491        move[3]>='0' && move[3]<='9' &&
5492        move[2]>='a' && move[2]<='x'    ) {
5493          /* output move, normal -> Shogi */
5494         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5495         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5496         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5497         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5498         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5499     }
5500     if (appData.debugMode) {
5501         fprintf(debugFP, "   out = '%s'\n", move);
5502     }
5503 }
5504
5505 char yy_textstr[8000];
5506
5507 /* Parser for moves from gnuchess, ICS, or user typein box */
5508 Boolean
5509 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5510 {
5511     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5512
5513     switch (*moveType) {
5514       case WhitePromotion:
5515       case BlackPromotion:
5516       case WhiteNonPromotion:
5517       case BlackNonPromotion:
5518       case NormalMove:
5519       case FirstLeg:
5520       case WhiteCapturesEnPassant:
5521       case BlackCapturesEnPassant:
5522       case WhiteKingSideCastle:
5523       case WhiteQueenSideCastle:
5524       case BlackKingSideCastle:
5525       case BlackQueenSideCastle:
5526       case WhiteKingSideCastleWild:
5527       case WhiteQueenSideCastleWild:
5528       case BlackKingSideCastleWild:
5529       case BlackQueenSideCastleWild:
5530       /* Code added by Tord: */
5531       case WhiteHSideCastleFR:
5532       case WhiteASideCastleFR:
5533       case BlackHSideCastleFR:
5534       case BlackASideCastleFR:
5535       /* End of code added by Tord */
5536       case IllegalMove:         /* bug or odd chess variant */
5537         if(currentMoveString[1] == '@') { // illegal drop
5538           *fromX = WhiteOnMove(moveNum) ?
5539             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5540             (int) CharToPiece(ToLower(currentMoveString[0]));
5541           goto drop;
5542         }
5543         *fromX = currentMoveString[0] - AAA;
5544         *fromY = currentMoveString[1] - ONE;
5545         *toX = currentMoveString[2] - AAA;
5546         *toY = currentMoveString[3] - ONE;
5547         *promoChar = currentMoveString[4];
5548         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5549             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5550     if (appData.debugMode) {
5551         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5552     }
5553             *fromX = *fromY = *toX = *toY = 0;
5554             return FALSE;
5555         }
5556         if (appData.testLegality) {
5557           return (*moveType != IllegalMove);
5558         } else {
5559           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5560                          // [HGM] lion: if this is a double move we are less critical
5561                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5562         }
5563
5564       case WhiteDrop:
5565       case BlackDrop:
5566         *fromX = *moveType == WhiteDrop ?
5567           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5568           (int) CharToPiece(ToLower(currentMoveString[0]));
5569       drop:
5570         *fromY = DROP_RANK;
5571         *toX = currentMoveString[2] - AAA;
5572         *toY = currentMoveString[3] - ONE;
5573         *promoChar = NULLCHAR;
5574         return TRUE;
5575
5576       case AmbiguousMove:
5577       case ImpossibleMove:
5578       case EndOfFile:
5579       case ElapsedTime:
5580       case Comment:
5581       case PGNTag:
5582       case NAG:
5583       case WhiteWins:
5584       case BlackWins:
5585       case GameIsDrawn:
5586       default:
5587     if (appData.debugMode) {
5588         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5589     }
5590         /* bug? */
5591         *fromX = *fromY = *toX = *toY = 0;
5592         *promoChar = NULLCHAR;
5593         return FALSE;
5594     }
5595 }
5596
5597 Boolean pushed = FALSE;
5598 char *lastParseAttempt;
5599
5600 void
5601 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5602 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5603   int fromX, fromY, toX, toY; char promoChar;
5604   ChessMove moveType;
5605   Boolean valid;
5606   int nr = 0;
5607
5608   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5609   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5610     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5611     pushed = TRUE;
5612   }
5613   endPV = forwardMostMove;
5614   do {
5615     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5616     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5617     lastParseAttempt = pv;
5618     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5619     if(!valid && nr == 0 &&
5620        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5621         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5622         // Hande case where played move is different from leading PV move
5623         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5624         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5625         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5626         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5627           endPV += 2; // if position different, keep this
5628           moveList[endPV-1][0] = fromX + AAA;
5629           moveList[endPV-1][1] = fromY + ONE;
5630           moveList[endPV-1][2] = toX + AAA;
5631           moveList[endPV-1][3] = toY + ONE;
5632           parseList[endPV-1][0] = NULLCHAR;
5633           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5634         }
5635       }
5636     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5637     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5638     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5639     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5640         valid++; // allow comments in PV
5641         continue;
5642     }
5643     nr++;
5644     if(endPV+1 > framePtr) break; // no space, truncate
5645     if(!valid) break;
5646     endPV++;
5647     CopyBoard(boards[endPV], boards[endPV-1]);
5648     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5649     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5650     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5651     CoordsToAlgebraic(boards[endPV - 1],
5652                              PosFlags(endPV - 1),
5653                              fromY, fromX, toY, toX, promoChar,
5654                              parseList[endPV - 1]);
5655   } while(valid);
5656   if(atEnd == 2) return; // used hidden, for PV conversion
5657   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5658   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5659   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5660                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5661   DrawPosition(TRUE, boards[currentMove]);
5662 }
5663
5664 int
5665 MultiPV (ChessProgramState *cps, int kind)
5666 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5667         int i;
5668         for(i=0; i<cps->nrOptions; i++) {
5669             char *s = cps->option[i].name;
5670             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5671             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5672                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5673         }
5674         return -1;
5675 }
5676
5677 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5678 static int multi, pv_margin;
5679 static ChessProgramState *activeCps;
5680
5681 Boolean
5682 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5683 {
5684         int startPV, lineStart, origIndex = index;
5685         char *p, buf2[MSG_SIZ];
5686         ChessProgramState *cps = (pane ? &second : &first);
5687
5688         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5689         lastX = x; lastY = y;
5690         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5691         lineStart = startPV = index;
5692         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5693         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5694         index = startPV;
5695         do{ while(buf[index] && buf[index] != '\n') index++;
5696         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5697         buf[index] = 0;
5698         if(lineStart == 0 && gameMode == AnalyzeMode) {
5699             int n = 0;
5700             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5701             if(n == 0) { // click not on "fewer" or "more"
5702                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5703                     pv_margin = cps->option[multi].value;
5704                     activeCps = cps; // non-null signals margin adjustment
5705                 }
5706             } else if((multi = MultiPV(cps, 1)) >= 0) {
5707                 n += cps->option[multi].value; if(n < 1) n = 1;
5708                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5709                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5710                 cps->option[multi].value = n;
5711                 *start = *end = 0;
5712                 return FALSE;
5713             }
5714         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5715                 ExcludeClick(origIndex - lineStart);
5716                 return FALSE;
5717         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5718                 Collapse(origIndex - lineStart);
5719                 return FALSE;
5720         }
5721         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5722         *start = startPV; *end = index-1;
5723         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5724         return TRUE;
5725 }
5726
5727 char *
5728 PvToSAN (char *pv)
5729 {
5730         static char buf[10*MSG_SIZ];
5731         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5732         *buf = NULLCHAR;
5733         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5734         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5735         for(i = forwardMostMove; i<endPV; i++){
5736             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5737             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5738             k += strlen(buf+k);
5739         }
5740         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5741         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5742         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5743         endPV = savedEnd;
5744         return buf;
5745 }
5746
5747 Boolean
5748 LoadPV (int x, int y)
5749 { // called on right mouse click to load PV
5750   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5751   lastX = x; lastY = y;
5752   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5753   extendGame = FALSE;
5754   return TRUE;
5755 }
5756
5757 void
5758 UnLoadPV ()
5759 {
5760   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5761   if(activeCps) {
5762     if(pv_margin != activeCps->option[multi].value) {
5763       char buf[MSG_SIZ];
5764       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5765       SendToProgram(buf, activeCps);
5766       activeCps->option[multi].value = pv_margin;
5767     }
5768     activeCps = NULL;
5769     return;
5770   }
5771   if(endPV < 0) return;
5772   if(appData.autoCopyPV) CopyFENToClipboard();
5773   endPV = -1;
5774   if(extendGame && currentMove > forwardMostMove) {
5775         Boolean saveAnimate = appData.animate;
5776         if(pushed) {
5777             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5778                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5779             } else storedGames--; // abandon shelved tail of original game
5780         }
5781         pushed = FALSE;
5782         forwardMostMove = currentMove;
5783         currentMove = oldFMM;
5784         appData.animate = FALSE;
5785         ToNrEvent(forwardMostMove);
5786         appData.animate = saveAnimate;
5787   }
5788   currentMove = forwardMostMove;
5789   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5790   ClearPremoveHighlights();
5791   DrawPosition(TRUE, boards[currentMove]);
5792 }
5793
5794 void
5795 MovePV (int x, int y, int h)
5796 { // step through PV based on mouse coordinates (called on mouse move)
5797   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5798
5799   if(activeCps) { // adjusting engine's multi-pv margin
5800     if(x > lastX) pv_margin++; else
5801     if(x < lastX) pv_margin -= (pv_margin > 0);
5802     if(x != lastX) {
5803       char buf[MSG_SIZ];
5804       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5805       DisplayMessage(buf, "");
5806     }
5807     lastX = x;
5808     return;
5809   }
5810   // we must somehow check if right button is still down (might be released off board!)
5811   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5812   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5813   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5814   if(!step) return;
5815   lastX = x; lastY = y;
5816
5817   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5818   if(endPV < 0) return;
5819   if(y < margin) step = 1; else
5820   if(y > h - margin) step = -1;
5821   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5822   currentMove += step;
5823   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5824   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5825                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5826   DrawPosition(FALSE, boards[currentMove]);
5827 }
5828
5829
5830 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5831 // All positions will have equal probability, but the current method will not provide a unique
5832 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5833 #define DARK 1
5834 #define LITE 2
5835 #define ANY 3
5836
5837 int squaresLeft[4];
5838 int piecesLeft[(int)BlackPawn];
5839 int seed, nrOfShuffles;
5840
5841 void
5842 GetPositionNumber ()
5843 {       // sets global variable seed
5844         int i;
5845
5846         seed = appData.defaultFrcPosition;
5847         if(seed < 0) { // randomize based on time for negative FRC position numbers
5848                 for(i=0; i<50; i++) seed += random();
5849                 seed = random() ^ random() >> 8 ^ random() << 8;
5850                 if(seed<0) seed = -seed;
5851         }
5852 }
5853
5854 int
5855 put (Board board, int pieceType, int rank, int n, int shade)
5856 // put the piece on the (n-1)-th empty squares of the given shade
5857 {
5858         int i;
5859
5860         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5861                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5862                         board[rank][i] = (ChessSquare) pieceType;
5863                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5864                         squaresLeft[ANY]--;
5865                         piecesLeft[pieceType]--;
5866                         return i;
5867                 }
5868         }
5869         return -1;
5870 }
5871
5872
5873 void
5874 AddOnePiece (Board board, int pieceType, int rank, int shade)
5875 // calculate where the next piece goes, (any empty square), and put it there
5876 {
5877         int i;
5878
5879         i = seed % squaresLeft[shade];
5880         nrOfShuffles *= squaresLeft[shade];
5881         seed /= squaresLeft[shade];
5882         put(board, pieceType, rank, i, shade);
5883 }
5884
5885 void
5886 AddTwoPieces (Board board, int pieceType, int rank)
5887 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5888 {
5889         int i, n=squaresLeft[ANY], j=n-1, k;
5890
5891         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5892         i = seed % k;  // pick one
5893         nrOfShuffles *= k;
5894         seed /= k;
5895         while(i >= j) i -= j--;
5896         j = n - 1 - j; i += j;
5897         put(board, pieceType, rank, j, ANY);
5898         put(board, pieceType, rank, i, ANY);
5899 }
5900
5901 void
5902 SetUpShuffle (Board board, int number)
5903 {
5904         int i, p, first=1;
5905
5906         GetPositionNumber(); nrOfShuffles = 1;
5907
5908         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5909         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5910         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5911
5912         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5913
5914         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5915             p = (int) board[0][i];
5916             if(p < (int) BlackPawn) piecesLeft[p] ++;
5917             board[0][i] = EmptySquare;
5918         }
5919
5920         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5921             // shuffles restricted to allow normal castling put KRR first
5922             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5923                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5924             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5925                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5926             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5927                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5928             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5929                 put(board, WhiteRook, 0, 0, ANY);
5930             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5931         }
5932
5933         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5934             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5935             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5936                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5937                 while(piecesLeft[p] >= 2) {
5938                     AddOnePiece(board, p, 0, LITE);
5939                     AddOnePiece(board, p, 0, DARK);
5940                 }
5941                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5942             }
5943
5944         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5945             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5946             // but we leave King and Rooks for last, to possibly obey FRC restriction
5947             if(p == (int)WhiteRook) continue;
5948             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5949             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5950         }
5951
5952         // now everything is placed, except perhaps King (Unicorn) and Rooks
5953
5954         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5955             // Last King gets castling rights
5956             while(piecesLeft[(int)WhiteUnicorn]) {
5957                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5958                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5959             }
5960
5961             while(piecesLeft[(int)WhiteKing]) {
5962                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5963                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5964             }
5965
5966
5967         } else {
5968             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5969             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5970         }
5971
5972         // Only Rooks can be left; simply place them all
5973         while(piecesLeft[(int)WhiteRook]) {
5974                 i = put(board, WhiteRook, 0, 0, ANY);
5975                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5976                         if(first) {
5977                                 first=0;
5978                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5979                         }
5980                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5981                 }
5982         }
5983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5984             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5985         }
5986
5987         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5988 }
5989
5990 int
5991 ptclen (const char *s, char *escapes)
5992 {
5993     int n = 0;
5994     if(!*escapes) return strlen(s);
5995     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
5996     return n;
5997 }
5998
5999 int
6000 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6001 /* [HGM] moved here from winboard.c because of its general usefulness */
6002 /*       Basically a safe strcpy that uses the last character as King */
6003 {
6004     int result = FALSE; int NrPieces, offs;
6005     unsigned char partner[EmptySquare];
6006
6007     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6008                     && NrPieces >= 12 && !(NrPieces&1)) {
6009         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
6010
6011         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6012         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6013             char *p, c=0;
6014             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6015             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6016             table[i + offs] = map[j++];
6017             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
6018             if(c) partner[i + offs] = table[i + offs], table[i + offs] = c;
6019         }
6020         table[(int) WhiteKing]  = map[j++];
6021         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6022             char *p, c=0;
6023             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6024             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6025             table[WHITE_TO_BLACK i + offs] = map[j++];
6026             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
6027             if(c) partner[WHITE_TO_BLACK i + offs] = table[WHITE_TO_BLACK i + offs], table[WHITE_TO_BLACK i + offs] = c;
6028         }
6029         table[(int) BlackKing]  = map[j++];
6030
6031
6032         if(*escapes) { // set up promotion pairing
6033             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6034             // pieceToChar entirely filled, so we can look up specified partners
6035             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6036                 int c = table[i];
6037                 if(c == '^' || c == '-') { // has specified partner
6038                     int p;
6039                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6040                     if(c == '^') table[i] = '+';
6041                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6042                 } else if(c == '*') promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6043             }
6044         }
6045
6046         result = TRUE;
6047     }
6048
6049     return result;
6050 }
6051
6052 int
6053 SetCharTable (unsigned char *table, const char * map)
6054 {
6055     return SetCharTableEsc(table, map, "");
6056 }
6057
6058 void
6059 Prelude (Board board)
6060 {       // [HGM] superchess: random selection of exo-pieces
6061         int i, j, k; ChessSquare p;
6062         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6063
6064         GetPositionNumber(); // use FRC position number
6065
6066         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6067             SetCharTable(pieceToChar, appData.pieceToCharTable);
6068             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6069                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6070         }
6071
6072         j = seed%4;                 seed /= 4;
6073         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6074         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6075         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6076         j = seed%3 + (seed%3 >= j); seed /= 3;
6077         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6078         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6079         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6080         j = seed%3;                 seed /= 3;
6081         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6082         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6083         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6084         j = seed%2 + (seed%2 >= j); seed /= 2;
6085         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6086         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6087         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6088         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6089         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6090         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6091         put(board, exoPieces[0],    0, 0, ANY);
6092         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6093 }
6094
6095 void
6096 InitPosition (int redraw)
6097 {
6098     ChessSquare (* pieces)[BOARD_FILES];
6099     int i, j, pawnRow=1, pieceRows=1, overrule,
6100     oldx = gameInfo.boardWidth,
6101     oldy = gameInfo.boardHeight,
6102     oldh = gameInfo.holdingsWidth;
6103     static int oldv;
6104
6105     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6106
6107     /* [AS] Initialize pv info list [HGM] and game status */
6108     {
6109         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6110             pvInfoList[i].depth = 0;
6111             boards[i][EP_STATUS] = EP_NONE;
6112             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6113         }
6114
6115         initialRulePlies = 0; /* 50-move counter start */
6116
6117         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6118         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6119     }
6120
6121
6122     /* [HGM] logic here is completely changed. In stead of full positions */
6123     /* the initialized data only consist of the two backranks. The switch */
6124     /* selects which one we will use, which is than copied to the Board   */
6125     /* initialPosition, which for the rest is initialized by Pawns and    */
6126     /* empty squares. This initial position is then copied to boards[0],  */
6127     /* possibly after shuffling, so that it remains available.            */
6128
6129     gameInfo.holdingsWidth = 0; /* default board sizes */
6130     gameInfo.boardWidth    = 8;
6131     gameInfo.boardHeight   = 8;
6132     gameInfo.holdingsSize  = 0;
6133     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6134     for(i=0; i<BOARD_FILES-6; i++)
6135       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6136     initialPosition[EP_STATUS] = EP_NONE;
6137     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6138     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6139     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6140          SetCharTable(pieceNickName, appData.pieceNickNames);
6141     else SetCharTable(pieceNickName, "............");
6142     pieces = FIDEArray;
6143
6144     switch (gameInfo.variant) {
6145     case VariantFischeRandom:
6146       shuffleOpenings = TRUE;
6147       appData.fischerCastling = TRUE;
6148     default:
6149       break;
6150     case VariantShatranj:
6151       pieces = ShatranjArray;
6152       nrCastlingRights = 0;
6153       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6154       break;
6155     case VariantMakruk:
6156       pieces = makrukArray;
6157       nrCastlingRights = 0;
6158       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6159       break;
6160     case VariantASEAN:
6161       pieces = aseanArray;
6162       nrCastlingRights = 0;
6163       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6164       break;
6165     case VariantTwoKings:
6166       pieces = twoKingsArray;
6167       break;
6168     case VariantGrand:
6169       pieces = GrandArray;
6170       nrCastlingRights = 0;
6171       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6172       gameInfo.boardWidth = 10;
6173       gameInfo.boardHeight = 10;
6174       gameInfo.holdingsSize = 7;
6175       break;
6176     case VariantCapaRandom:
6177       shuffleOpenings = TRUE;
6178       appData.fischerCastling = TRUE;
6179     case VariantCapablanca:
6180       pieces = CapablancaArray;
6181       gameInfo.boardWidth = 10;
6182       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6183       break;
6184     case VariantGothic:
6185       pieces = GothicArray;
6186       gameInfo.boardWidth = 10;
6187       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6188       break;
6189     case VariantSChess:
6190       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6191       gameInfo.holdingsSize = 7;
6192       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6193       break;
6194     case VariantJanus:
6195       pieces = JanusArray;
6196       gameInfo.boardWidth = 10;
6197       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6198       nrCastlingRights = 6;
6199         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6200         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6201         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6202         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6203         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6204         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6205       break;
6206     case VariantFalcon:
6207       pieces = FalconArray;
6208       gameInfo.boardWidth = 10;
6209       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6210       break;
6211     case VariantXiangqi:
6212       pieces = XiangqiArray;
6213       gameInfo.boardWidth  = 9;
6214       gameInfo.boardHeight = 10;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6217       break;
6218     case VariantShogi:
6219       pieces = ShogiArray;
6220       gameInfo.boardWidth  = 9;
6221       gameInfo.boardHeight = 9;
6222       gameInfo.holdingsSize = 7;
6223       nrCastlingRights = 0;
6224       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6225       break;
6226     case VariantChu:
6227       pieces = ChuArray; pieceRows = 3;
6228       gameInfo.boardWidth  = 12;
6229       gameInfo.boardHeight = 12;
6230       nrCastlingRights = 0;
6231       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6232                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6233       break;
6234     case VariantCourier:
6235       pieces = CourierArray;
6236       gameInfo.boardWidth  = 12;
6237       nrCastlingRights = 0;
6238       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6239       break;
6240     case VariantKnightmate:
6241       pieces = KnightmateArray;
6242       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6243       break;
6244     case VariantSpartan:
6245       pieces = SpartanArray;
6246       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6247       break;
6248     case VariantLion:
6249       pieces = lionArray;
6250       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6251       break;
6252     case VariantChuChess:
6253       pieces = ChuChessArray;
6254       gameInfo.boardWidth = 10;
6255       gameInfo.boardHeight = 10;
6256       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6257       break;
6258     case VariantFairy:
6259       pieces = fairyArray;
6260       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6261       break;
6262     case VariantGreat:
6263       pieces = GreatArray;
6264       gameInfo.boardWidth = 10;
6265       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6266       gameInfo.holdingsSize = 8;
6267       break;
6268     case VariantSuper:
6269       pieces = FIDEArray;
6270       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6271       gameInfo.holdingsSize = 8;
6272       startedFromSetupPosition = TRUE;
6273       break;
6274     case VariantCrazyhouse:
6275     case VariantBughouse:
6276       pieces = FIDEArray;
6277       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6278       gameInfo.holdingsSize = 5;
6279       break;
6280     case VariantWildCastle:
6281       pieces = FIDEArray;
6282       /* !!?shuffle with kings guaranteed to be on d or e file */
6283       shuffleOpenings = 1;
6284       break;
6285     case VariantNoCastle:
6286       pieces = FIDEArray;
6287       nrCastlingRights = 0;
6288       /* !!?unconstrained back-rank shuffle */
6289       shuffleOpenings = 1;
6290       break;
6291     }
6292
6293     overrule = 0;
6294     if(appData.NrFiles >= 0) {
6295         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6296         gameInfo.boardWidth = appData.NrFiles;
6297     }
6298     if(appData.NrRanks >= 0) {
6299         gameInfo.boardHeight = appData.NrRanks;
6300     }
6301     if(appData.holdingsSize >= 0) {
6302         i = appData.holdingsSize;
6303         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6304         gameInfo.holdingsSize = i;
6305     }
6306     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6307     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6308         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6309
6310     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6311     if(pawnRow < 1) pawnRow = 1;
6312     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6313        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6314     if(gameInfo.variant == VariantChu) pawnRow = 3;
6315
6316     /* User pieceToChar list overrules defaults */
6317     if(appData.pieceToCharTable != NULL)
6318         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6319
6320     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6321
6322         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6323             s = (ChessSquare) 0; /* account holding counts in guard band */
6324         for( i=0; i<BOARD_HEIGHT; i++ )
6325             initialPosition[i][j] = s;
6326
6327         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6328         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6329         initialPosition[pawnRow][j] = WhitePawn;
6330         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6331         if(gameInfo.variant == VariantXiangqi) {
6332             if(j&1) {
6333                 initialPosition[pawnRow][j] =
6334                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6335                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6336                    initialPosition[2][j] = WhiteCannon;
6337                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6338                 }
6339             }
6340         }
6341         if(gameInfo.variant == VariantChu) {
6342              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6343                initialPosition[pawnRow+1][j] = WhiteCobra,
6344                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6345              for(i=1; i<pieceRows; i++) {
6346                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6347                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6348              }
6349         }
6350         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6351             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6352                initialPosition[0][j] = WhiteRook;
6353                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6354             }
6355         }
6356         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6357     }
6358     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6359     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6360
6361             j=BOARD_LEFT+1;
6362             initialPosition[1][j] = WhiteBishop;
6363             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6364             j=BOARD_RGHT-2;
6365             initialPosition[1][j] = WhiteRook;
6366             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6367     }
6368
6369     if( nrCastlingRights == -1) {
6370         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6371         /*       This sets default castling rights from none to normal corners   */
6372         /* Variants with other castling rights must set them themselves above    */
6373         nrCastlingRights = 6;
6374
6375         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6376         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6377         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6378         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6379         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6380         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6381      }
6382
6383      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6384      if(gameInfo.variant == VariantGreat) { // promotion commoners
6385         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6386         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6387         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6388         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6389      }
6390      if( gameInfo.variant == VariantSChess ) {
6391       initialPosition[1][0] = BlackMarshall;
6392       initialPosition[2][0] = BlackAngel;
6393       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6394       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6395       initialPosition[1][1] = initialPosition[2][1] =
6396       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6397      }
6398   if (appData.debugMode) {
6399     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6400   }
6401     if(shuffleOpenings) {
6402         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6403         startedFromSetupPosition = TRUE;
6404     }
6405     if(startedFromPositionFile) {
6406       /* [HGM] loadPos: use PositionFile for every new game */
6407       CopyBoard(initialPosition, filePosition);
6408       for(i=0; i<nrCastlingRights; i++)
6409           initialRights[i] = filePosition[CASTLING][i];
6410       startedFromSetupPosition = TRUE;
6411     }
6412
6413     CopyBoard(boards[0], initialPosition);
6414
6415     if(oldx != gameInfo.boardWidth ||
6416        oldy != gameInfo.boardHeight ||
6417        oldv != gameInfo.variant ||
6418        oldh != gameInfo.holdingsWidth
6419                                          )
6420             InitDrawingSizes(-2 ,0);
6421
6422     oldv = gameInfo.variant;
6423     if (redraw)
6424       DrawPosition(TRUE, boards[currentMove]);
6425 }
6426
6427 void
6428 SendBoard (ChessProgramState *cps, int moveNum)
6429 {
6430     char message[MSG_SIZ];
6431
6432     if (cps->useSetboard) {
6433       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6434       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6435       SendToProgram(message, cps);
6436       free(fen);
6437
6438     } else {
6439       ChessSquare *bp;
6440       int i, j, left=0, right=BOARD_WIDTH;
6441       /* Kludge to set black to move, avoiding the troublesome and now
6442        * deprecated "black" command.
6443        */
6444       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6445         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6446
6447       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6448
6449       SendToProgram("edit\n", cps);
6450       SendToProgram("#\n", cps);
6451       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6452         bp = &boards[moveNum][i][left];
6453         for (j = left; j < right; j++, bp++) {
6454           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6455           if ((int) *bp < (int) BlackPawn) {
6456             if(j == BOARD_RGHT+1)
6457                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6458             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6459             if(message[0] == '+' || message[0] == '~') {
6460               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6461                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6462                         AAA + j, ONE + i - '0');
6463             }
6464             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6465                 message[1] = BOARD_RGHT   - 1 - j + '1';
6466                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6467             }
6468             SendToProgram(message, cps);
6469           }
6470         }
6471       }
6472
6473       SendToProgram("c\n", cps);
6474       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6475         bp = &boards[moveNum][i][left];
6476         for (j = left; j < right; j++, bp++) {
6477           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6478           if (((int) *bp != (int) EmptySquare)
6479               && ((int) *bp >= (int) BlackPawn)) {
6480             if(j == BOARD_LEFT-2)
6481                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6482             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6483                     AAA + j, ONE + i - '0');
6484             if(message[0] == '+' || message[0] == '~') {
6485               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6486                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6487                         AAA + j, ONE + i - '0');
6488             }
6489             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6490                 message[1] = BOARD_RGHT   - 1 - j + '1';
6491                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6492             }
6493             SendToProgram(message, cps);
6494           }
6495         }
6496       }
6497
6498       SendToProgram(".\n", cps);
6499     }
6500     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6501 }
6502
6503 char exclusionHeader[MSG_SIZ];
6504 int exCnt, excludePtr;
6505 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6506 static Exclusion excluTab[200];
6507 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6508
6509 static void
6510 WriteMap (int s)
6511 {
6512     int j;
6513     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6514     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6515 }
6516
6517 static void
6518 ClearMap ()
6519 {
6520     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6521     excludePtr = 24; exCnt = 0;
6522     WriteMap(0);
6523 }
6524
6525 static void
6526 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6527 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6528     char buf[2*MOVE_LEN], *p;
6529     Exclusion *e = excluTab;
6530     int i;
6531     for(i=0; i<exCnt; i++)
6532         if(e[i].ff == fromX && e[i].fr == fromY &&
6533            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6534     if(i == exCnt) { // was not in exclude list; add it
6535         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6536         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6537             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6538             return; // abort
6539         }
6540         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6541         excludePtr++; e[i].mark = excludePtr++;
6542         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6543         exCnt++;
6544     }
6545     exclusionHeader[e[i].mark] = state;
6546 }
6547
6548 static int
6549 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6550 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6551     char buf[MSG_SIZ];
6552     int j, k;
6553     ChessMove moveType;
6554     if((signed char)promoChar == -1) { // kludge to indicate best move
6555         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6556             return 1; // if unparsable, abort
6557     }
6558     // update exclusion map (resolving toggle by consulting existing state)
6559     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6560     j = k%8; k >>= 3;
6561     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6562     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6563          excludeMap[k] |=   1<<j;
6564     else excludeMap[k] &= ~(1<<j);
6565     // update header
6566     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6567     // inform engine
6568     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6569     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6570     SendToBoth(buf);
6571     return (state == '+');
6572 }
6573
6574 static void
6575 ExcludeClick (int index)
6576 {
6577     int i, j;
6578     Exclusion *e = excluTab;
6579     if(index < 25) { // none, best or tail clicked
6580         if(index < 13) { // none: include all
6581             WriteMap(0); // clear map
6582             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6583             SendToBoth("include all\n"); // and inform engine
6584         } else if(index > 18) { // tail
6585             if(exclusionHeader[19] == '-') { // tail was excluded
6586                 SendToBoth("include all\n");
6587                 WriteMap(0); // clear map completely
6588                 // now re-exclude selected moves
6589                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6590                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6591             } else { // tail was included or in mixed state
6592                 SendToBoth("exclude all\n");
6593                 WriteMap(0xFF); // fill map completely
6594                 // now re-include selected moves
6595                 j = 0; // count them
6596                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6597                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6598                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6599             }
6600         } else { // best
6601             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6602         }
6603     } else {
6604         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6605             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6606             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6607             break;
6608         }
6609     }
6610 }
6611
6612 ChessSquare
6613 DefaultPromoChoice (int white)
6614 {
6615     ChessSquare result;
6616     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6617        gameInfo.variant == VariantMakruk)
6618         result = WhiteFerz; // no choice
6619     else if(gameInfo.variant == VariantASEAN)
6620         result = WhiteRook; // no choice
6621     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6622         result= WhiteKing; // in Suicide Q is the last thing we want
6623     else if(gameInfo.variant == VariantSpartan)
6624         result = white ? WhiteQueen : WhiteAngel;
6625     else result = WhiteQueen;
6626     if(!white) result = WHITE_TO_BLACK result;
6627     return result;
6628 }
6629
6630 static int autoQueen; // [HGM] oneclick
6631
6632 int
6633 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6634 {
6635     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6636     /* [HGM] add Shogi promotions */
6637     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6638     ChessSquare piece, partner;
6639     ChessMove moveType;
6640     Boolean premove;
6641
6642     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6643     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6644
6645     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6646       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6647         return FALSE;
6648
6649     piece = boards[currentMove][fromY][fromX];
6650     if(gameInfo.variant == VariantChu) {
6651         promotionZoneSize = BOARD_HEIGHT/3;
6652         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6653     } else if(gameInfo.variant == VariantShogi) {
6654         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6655         highestPromotingPiece = (int)WhiteAlfil;
6656     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6657         promotionZoneSize = 3;
6658     }
6659
6660     // Treat Lance as Pawn when it is not representing Amazon or Lance
6661     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6662         if(piece == WhiteLance) piece = WhitePawn; else
6663         if(piece == BlackLance) piece = BlackPawn;
6664     }
6665
6666     // next weed out all moves that do not touch the promotion zone at all
6667     if((int)piece >= BlackPawn) {
6668         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6669              return FALSE;
6670         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6671         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6672     } else {
6673         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6674            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6675         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6676              return FALSE;
6677     }
6678
6679     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6680
6681     // weed out mandatory Shogi promotions
6682     if(gameInfo.variant == VariantShogi) {
6683         if(piece >= BlackPawn) {
6684             if(toY == 0 && piece == BlackPawn ||
6685                toY == 0 && piece == BlackQueen ||
6686                toY <= 1 && piece == BlackKnight) {
6687                 *promoChoice = '+';
6688                 return FALSE;
6689             }
6690         } else {
6691             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6692                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6693                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6694                 *promoChoice = '+';
6695                 return FALSE;
6696             }
6697         }
6698     }
6699
6700     // weed out obviously illegal Pawn moves
6701     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6702         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6703         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6704         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6705         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6706         // note we are not allowed to test for valid (non-)capture, due to premove
6707     }
6708
6709     // we either have a choice what to promote to, or (in Shogi) whether to promote
6710     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6711        gameInfo.variant == VariantMakruk) {
6712         ChessSquare p=BlackFerz;  // no choice
6713         while(p < EmptySquare) {  //but make sure we use piece that exists
6714             *promoChoice = PieceToChar(p++);
6715             if(*promoChoice != '.') break;
6716         }
6717         return FALSE;
6718     }
6719     // no sense asking what we must promote to if it is going to explode...
6720     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6721         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6722         return FALSE;
6723     }
6724     // give caller the default choice even if we will not make it
6725     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6726     partner = piece; // pieces can promote if the pieceToCharTable says so
6727     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6728     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6729     if(        sweepSelect && gameInfo.variant != VariantGreat
6730                            && gameInfo.variant != VariantGrand
6731                            && gameInfo.variant != VariantSuper) return FALSE;
6732     if(autoQueen) return FALSE; // predetermined
6733
6734     // suppress promotion popup on illegal moves that are not premoves
6735     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6736               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6737     if(appData.testLegality && !premove) {
6738         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6739                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6740         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6741         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6742             return FALSE;
6743     }
6744
6745     return TRUE;
6746 }
6747
6748 int
6749 InPalace (int row, int column)
6750 {   /* [HGM] for Xiangqi */
6751     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6752          column < (BOARD_WIDTH + 4)/2 &&
6753          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6754     return FALSE;
6755 }
6756
6757 int
6758 PieceForSquare (int x, int y)
6759 {
6760   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6761      return -1;
6762   else
6763      return boards[currentMove][y][x];
6764 }
6765
6766 int
6767 OKToStartUserMove (int x, int y)
6768 {
6769     ChessSquare from_piece;
6770     int white_piece;
6771
6772     if (matchMode) return FALSE;
6773     if (gameMode == EditPosition) return TRUE;
6774
6775     if (x >= 0 && y >= 0)
6776       from_piece = boards[currentMove][y][x];
6777     else
6778       from_piece = EmptySquare;
6779
6780     if (from_piece == EmptySquare) return FALSE;
6781
6782     white_piece = (int)from_piece >= (int)WhitePawn &&
6783       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6784
6785     switch (gameMode) {
6786       case AnalyzeFile:
6787       case TwoMachinesPlay:
6788       case EndOfGame:
6789         return FALSE;
6790
6791       case IcsObserving:
6792       case IcsIdle:
6793         return FALSE;
6794
6795       case MachinePlaysWhite:
6796       case IcsPlayingBlack:
6797         if (appData.zippyPlay) return FALSE;
6798         if (white_piece) {
6799             DisplayMoveError(_("You are playing Black"));
6800             return FALSE;
6801         }
6802         break;
6803
6804       case MachinePlaysBlack:
6805       case IcsPlayingWhite:
6806         if (appData.zippyPlay) return FALSE;
6807         if (!white_piece) {
6808             DisplayMoveError(_("You are playing White"));
6809             return FALSE;
6810         }
6811         break;
6812
6813       case PlayFromGameFile:
6814             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6815       case EditGame:
6816         if (!white_piece && WhiteOnMove(currentMove)) {
6817             DisplayMoveError(_("It is White's turn"));
6818             return FALSE;
6819         }
6820         if (white_piece && !WhiteOnMove(currentMove)) {
6821             DisplayMoveError(_("It is Black's turn"));
6822             return FALSE;
6823         }
6824         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6825             /* Editing correspondence game history */
6826             /* Could disallow this or prompt for confirmation */
6827             cmailOldMove = -1;
6828         }
6829         break;
6830
6831       case BeginningOfGame:
6832         if (appData.icsActive) return FALSE;
6833         if (!appData.noChessProgram) {
6834             if (!white_piece) {
6835                 DisplayMoveError(_("You are playing White"));
6836                 return FALSE;
6837             }
6838         }
6839         break;
6840
6841       case Training:
6842         if (!white_piece && WhiteOnMove(currentMove)) {
6843             DisplayMoveError(_("It is White's turn"));
6844             return FALSE;
6845         }
6846         if (white_piece && !WhiteOnMove(currentMove)) {
6847             DisplayMoveError(_("It is Black's turn"));
6848             return FALSE;
6849         }
6850         break;
6851
6852       default:
6853       case IcsExamining:
6854         break;
6855     }
6856     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6857         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6858         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6859         && gameMode != AnalyzeFile && gameMode != Training) {
6860         DisplayMoveError(_("Displayed position is not current"));
6861         return FALSE;
6862     }
6863     return TRUE;
6864 }
6865
6866 Boolean
6867 OnlyMove (int *x, int *y, Boolean captures)
6868 {
6869     DisambiguateClosure cl;
6870     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6871     switch(gameMode) {
6872       case MachinePlaysBlack:
6873       case IcsPlayingWhite:
6874       case BeginningOfGame:
6875         if(!WhiteOnMove(currentMove)) return FALSE;
6876         break;
6877       case MachinePlaysWhite:
6878       case IcsPlayingBlack:
6879         if(WhiteOnMove(currentMove)) return FALSE;
6880         break;
6881       case EditGame:
6882         break;
6883       default:
6884         return FALSE;
6885     }
6886     cl.pieceIn = EmptySquare;
6887     cl.rfIn = *y;
6888     cl.ffIn = *x;
6889     cl.rtIn = -1;
6890     cl.ftIn = -1;
6891     cl.promoCharIn = NULLCHAR;
6892     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6893     if( cl.kind == NormalMove ||
6894         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6895         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6896         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6897       fromX = cl.ff;
6898       fromY = cl.rf;
6899       *x = cl.ft;
6900       *y = cl.rt;
6901       return TRUE;
6902     }
6903     if(cl.kind != ImpossibleMove) return FALSE;
6904     cl.pieceIn = EmptySquare;
6905     cl.rfIn = -1;
6906     cl.ffIn = -1;
6907     cl.rtIn = *y;
6908     cl.ftIn = *x;
6909     cl.promoCharIn = NULLCHAR;
6910     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6911     if( cl.kind == NormalMove ||
6912         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6913         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6914         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6915       fromX = cl.ff;
6916       fromY = cl.rf;
6917       *x = cl.ft;
6918       *y = cl.rt;
6919       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6920       return TRUE;
6921     }
6922     return FALSE;
6923 }
6924
6925 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6926 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6927 int lastLoadGameUseList = FALSE;
6928 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6929 ChessMove lastLoadGameStart = EndOfFile;
6930 int doubleClick;
6931 Boolean addToBookFlag;
6932
6933 void
6934 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6935 {
6936     ChessMove moveType;
6937     ChessSquare pup;
6938     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6939
6940     /* Check if the user is playing in turn.  This is complicated because we
6941        let the user "pick up" a piece before it is his turn.  So the piece he
6942        tried to pick up may have been captured by the time he puts it down!
6943        Therefore we use the color the user is supposed to be playing in this
6944        test, not the color of the piece that is currently on the starting
6945        square---except in EditGame mode, where the user is playing both
6946        sides; fortunately there the capture race can't happen.  (It can
6947        now happen in IcsExamining mode, but that's just too bad.  The user
6948        will get a somewhat confusing message in that case.)
6949        */
6950
6951     switch (gameMode) {
6952       case AnalyzeFile:
6953       case TwoMachinesPlay:
6954       case EndOfGame:
6955       case IcsObserving:
6956       case IcsIdle:
6957         /* We switched into a game mode where moves are not accepted,
6958            perhaps while the mouse button was down. */
6959         return;
6960
6961       case MachinePlaysWhite:
6962         /* User is moving for Black */
6963         if (WhiteOnMove(currentMove)) {
6964             DisplayMoveError(_("It is White's turn"));
6965             return;
6966         }
6967         break;
6968
6969       case MachinePlaysBlack:
6970         /* User is moving for White */
6971         if (!WhiteOnMove(currentMove)) {
6972             DisplayMoveError(_("It is Black's turn"));
6973             return;
6974         }
6975         break;
6976
6977       case PlayFromGameFile:
6978             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6979       case EditGame:
6980       case IcsExamining:
6981       case BeginningOfGame:
6982       case AnalyzeMode:
6983       case Training:
6984         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6985         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6986             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6987             /* User is moving for Black */
6988             if (WhiteOnMove(currentMove)) {
6989                 DisplayMoveError(_("It is White's turn"));
6990                 return;
6991             }
6992         } else {
6993             /* User is moving for White */
6994             if (!WhiteOnMove(currentMove)) {
6995                 DisplayMoveError(_("It is Black's turn"));
6996                 return;
6997             }
6998         }
6999         break;
7000
7001       case IcsPlayingBlack:
7002         /* User is moving for Black */
7003         if (WhiteOnMove(currentMove)) {
7004             if (!appData.premove) {
7005                 DisplayMoveError(_("It is White's turn"));
7006             } else if (toX >= 0 && toY >= 0) {
7007                 premoveToX = toX;
7008                 premoveToY = toY;
7009                 premoveFromX = fromX;
7010                 premoveFromY = fromY;
7011                 premovePromoChar = promoChar;
7012                 gotPremove = 1;
7013                 if (appData.debugMode)
7014                     fprintf(debugFP, "Got premove: fromX %d,"
7015                             "fromY %d, toX %d, toY %d\n",
7016                             fromX, fromY, toX, toY);
7017             }
7018             return;
7019         }
7020         break;
7021
7022       case IcsPlayingWhite:
7023         /* User is moving for White */
7024         if (!WhiteOnMove(currentMove)) {
7025             if (!appData.premove) {
7026                 DisplayMoveError(_("It is Black's turn"));
7027             } else if (toX >= 0 && toY >= 0) {
7028                 premoveToX = toX;
7029                 premoveToY = toY;
7030                 premoveFromX = fromX;
7031                 premoveFromY = fromY;
7032                 premovePromoChar = promoChar;
7033                 gotPremove = 1;
7034                 if (appData.debugMode)
7035                     fprintf(debugFP, "Got premove: fromX %d,"
7036                             "fromY %d, toX %d, toY %d\n",
7037                             fromX, fromY, toX, toY);
7038             }
7039             return;
7040         }
7041         break;
7042
7043       default:
7044         break;
7045
7046       case EditPosition:
7047         /* EditPosition, empty square, or different color piece;
7048            click-click move is possible */
7049         if (toX == -2 || toY == -2) {
7050             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7051             DrawPosition(FALSE, boards[currentMove]);
7052             return;
7053         } else if (toX >= 0 && toY >= 0) {
7054             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7055                 ChessSquare q, p = boards[0][rf][ff];
7056                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7057                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7058                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7059                 if(PieceToChar(q) == '+') gatingPiece = p;
7060             }
7061             boards[0][toY][toX] = boards[0][fromY][fromX];
7062             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7063                 if(boards[0][fromY][0] != EmptySquare) {
7064                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7065                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7066                 }
7067             } else
7068             if(fromX == BOARD_RGHT+1) {
7069                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7070                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7071                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7072                 }
7073             } else
7074             boards[0][fromY][fromX] = gatingPiece;
7075             DrawPosition(FALSE, boards[currentMove]);
7076             return;
7077         }
7078         return;
7079     }
7080
7081     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7082     pup = boards[currentMove][toY][toX];
7083
7084     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7085     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7086          if( pup != EmptySquare ) return;
7087          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7088            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7089                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7090            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7091            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7092            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7093            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7094          fromY = DROP_RANK;
7095     }
7096
7097     /* [HGM] always test for legality, to get promotion info */
7098     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7099                                          fromY, fromX, toY, toX, promoChar);
7100
7101     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7102
7103     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7104
7105     /* [HGM] but possibly ignore an IllegalMove result */
7106     if (appData.testLegality) {
7107         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7108             DisplayMoveError(_("Illegal move"));
7109             return;
7110         }
7111     }
7112
7113     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7114         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7115              ClearPremoveHighlights(); // was included
7116         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7117         return;
7118     }
7119
7120     if(addToBookFlag) { // adding moves to book
7121         char buf[MSG_SIZ], move[MSG_SIZ];
7122         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7123         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7124         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7125         AddBookMove(buf);
7126         addToBookFlag = FALSE;
7127         ClearHighlights();
7128         return;
7129     }
7130
7131     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7132 }
7133
7134 /* Common tail of UserMoveEvent and DropMenuEvent */
7135 int
7136 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7137 {
7138     char *bookHit = 0;
7139
7140     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7141         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7142         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7143         if(WhiteOnMove(currentMove)) {
7144             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7145         } else {
7146             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7147         }
7148     }
7149
7150     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7151        move type in caller when we know the move is a legal promotion */
7152     if(moveType == NormalMove && promoChar)
7153         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7154
7155     /* [HGM] <popupFix> The following if has been moved here from
7156        UserMoveEvent(). Because it seemed to belong here (why not allow
7157        piece drops in training games?), and because it can only be
7158        performed after it is known to what we promote. */
7159     if (gameMode == Training) {
7160       /* compare the move played on the board to the next move in the
7161        * game. If they match, display the move and the opponent's response.
7162        * If they don't match, display an error message.
7163        */
7164       int saveAnimate;
7165       Board testBoard;
7166       CopyBoard(testBoard, boards[currentMove]);
7167       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7168
7169       if (CompareBoards(testBoard, boards[currentMove+1])) {
7170         ForwardInner(currentMove+1);
7171
7172         /* Autoplay the opponent's response.
7173          * if appData.animate was TRUE when Training mode was entered,
7174          * the response will be animated.
7175          */
7176         saveAnimate = appData.animate;
7177         appData.animate = animateTraining;
7178         ForwardInner(currentMove+1);
7179         appData.animate = saveAnimate;
7180
7181         /* check for the end of the game */
7182         if (currentMove >= forwardMostMove) {
7183           gameMode = PlayFromGameFile;
7184           ModeHighlight();
7185           SetTrainingModeOff();
7186           DisplayInformation(_("End of game"));
7187         }
7188       } else {
7189         DisplayError(_("Incorrect move"), 0);
7190       }
7191       return 1;
7192     }
7193
7194   /* Ok, now we know that the move is good, so we can kill
7195      the previous line in Analysis Mode */
7196   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7197                                 && currentMove < forwardMostMove) {
7198     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7199     else forwardMostMove = currentMove;
7200   }
7201
7202   ClearMap();
7203
7204   /* If we need the chess program but it's dead, restart it */
7205   ResurrectChessProgram();
7206
7207   /* A user move restarts a paused game*/
7208   if (pausing)
7209     PauseEvent();
7210
7211   thinkOutput[0] = NULLCHAR;
7212
7213   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7214
7215   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7216     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7217     return 1;
7218   }
7219
7220   if (gameMode == BeginningOfGame) {
7221     if (appData.noChessProgram) {
7222       gameMode = EditGame;
7223       SetGameInfo();
7224     } else {
7225       char buf[MSG_SIZ];
7226       gameMode = MachinePlaysBlack;
7227       StartClocks();
7228       SetGameInfo();
7229       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7230       DisplayTitle(buf);
7231       if (first.sendName) {
7232         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7233         SendToProgram(buf, &first);
7234       }
7235       StartClocks();
7236     }
7237     ModeHighlight();
7238   }
7239
7240   /* Relay move to ICS or chess engine */
7241   if (appData.icsActive) {
7242     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7243         gameMode == IcsExamining) {
7244       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7245         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7246         SendToICS("draw ");
7247         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7248       }
7249       // also send plain move, in case ICS does not understand atomic claims
7250       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7251       ics_user_moved = 1;
7252     }
7253   } else {
7254     if (first.sendTime && (gameMode == BeginningOfGame ||
7255                            gameMode == MachinePlaysWhite ||
7256                            gameMode == MachinePlaysBlack)) {
7257       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7258     }
7259     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7260          // [HGM] book: if program might be playing, let it use book
7261         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7262         first.maybeThinking = TRUE;
7263     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7264         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7265         SendBoard(&first, currentMove+1);
7266         if(second.analyzing) {
7267             if(!second.useSetboard) SendToProgram("undo\n", &second);
7268             SendBoard(&second, currentMove+1);
7269         }
7270     } else {
7271         SendMoveToProgram(forwardMostMove-1, &first);
7272         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7273     }
7274     if (currentMove == cmailOldMove + 1) {
7275       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7276     }
7277   }
7278
7279   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7280
7281   switch (gameMode) {
7282   case EditGame:
7283     if(appData.testLegality)
7284     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7285     case MT_NONE:
7286     case MT_CHECK:
7287       break;
7288     case MT_CHECKMATE:
7289     case MT_STAINMATE:
7290       if (WhiteOnMove(currentMove)) {
7291         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7292       } else {
7293         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7294       }
7295       break;
7296     case MT_STALEMATE:
7297       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7298       break;
7299     }
7300     break;
7301
7302   case MachinePlaysBlack:
7303   case MachinePlaysWhite:
7304     /* disable certain menu options while machine is thinking */
7305     SetMachineThinkingEnables();
7306     break;
7307
7308   default:
7309     break;
7310   }
7311
7312   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7313   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7314
7315   if(bookHit) { // [HGM] book: simulate book reply
7316         static char bookMove[MSG_SIZ]; // a bit generous?
7317
7318         programStats.nodes = programStats.depth = programStats.time =
7319         programStats.score = programStats.got_only_move = 0;
7320         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7321
7322         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7323         strcat(bookMove, bookHit);
7324         HandleMachineMove(bookMove, &first);
7325   }
7326   return 1;
7327 }
7328
7329 void
7330 MarkByFEN(char *fen)
7331 {
7332         int r, f;
7333         if(!appData.markers || !appData.highlightDragging) return;
7334         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7335         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7336         while(*fen) {
7337             int s = 0;
7338             marker[r][f] = 0;
7339             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7340             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7341             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7342             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7343             if(*fen == 'T') marker[r][f++] = 0; else
7344             if(*fen == 'Y') marker[r][f++] = 1; else
7345             if(*fen == 'G') marker[r][f++] = 3; else
7346             if(*fen == 'B') marker[r][f++] = 4; else
7347             if(*fen == 'C') marker[r][f++] = 5; else
7348             if(*fen == 'M') marker[r][f++] = 6; else
7349             if(*fen == 'W') marker[r][f++] = 7; else
7350             if(*fen == 'D') marker[r][f++] = 8; else
7351             if(*fen == 'R') marker[r][f++] = 2; else {
7352                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7353               f += s; fen -= s>0;
7354             }
7355             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7356             if(r < 0) break;
7357             fen++;
7358         }
7359         DrawPosition(TRUE, NULL);
7360 }
7361
7362 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7363
7364 void
7365 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7366 {
7367     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7368     Markers *m = (Markers *) closure;
7369     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7370         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7371                          || kind == WhiteCapturesEnPassant
7372                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7373     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7374 }
7375
7376 static int hoverSavedValid;
7377
7378 void
7379 MarkTargetSquares (int clear)
7380 {
7381   int x, y, sum=0;
7382   if(clear) { // no reason to ever suppress clearing
7383     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7384     hoverSavedValid = 0;
7385     if(!sum) return; // nothing was cleared,no redraw needed
7386   } else {
7387     int capt = 0;
7388     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7389        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7390     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7391     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7392       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7393       if(capt)
7394       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7395     }
7396   }
7397   DrawPosition(FALSE, NULL);
7398 }
7399
7400 int
7401 Explode (Board board, int fromX, int fromY, int toX, int toY)
7402 {
7403     if(gameInfo.variant == VariantAtomic &&
7404        (board[toY][toX] != EmptySquare ||                     // capture?
7405         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7406                          board[fromY][fromX] == BlackPawn   )
7407       )) {
7408         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7409         return TRUE;
7410     }
7411     return FALSE;
7412 }
7413
7414 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7415
7416 int
7417 CanPromote (ChessSquare piece, int y)
7418 {
7419         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7420         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7421         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7422         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7423            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7424            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7425          gameInfo.variant == VariantMakruk) return FALSE;
7426         return (piece == BlackPawn && y <= zone ||
7427                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7428                 piece == BlackLance && y <= zone ||
7429                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7430 }
7431
7432 void
7433 HoverEvent (int xPix, int yPix, int x, int y)
7434 {
7435         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7436         int r, f;
7437         if(!first.highlight) return;
7438         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7439         if(x == oldX && y == oldY) return; // only do something if we enter new square
7440         oldFromX = fromX; oldFromY = fromY;
7441         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7442           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7443             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7444           hoverSavedValid = 1;
7445         } else if(oldX != x || oldY != y) {
7446           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7447           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7448           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7449             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7450           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7451             char buf[MSG_SIZ];
7452             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7453             SendToProgram(buf, &first);
7454           }
7455           oldX = x; oldY = y;
7456 //        SetHighlights(fromX, fromY, x, y);
7457         }
7458 }
7459
7460 void ReportClick(char *action, int x, int y)
7461 {
7462         char buf[MSG_SIZ]; // Inform engine of what user does
7463         int r, f;
7464         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7465           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7466             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7467         if(!first.highlight || gameMode == EditPosition) return;
7468         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7469         SendToProgram(buf, &first);
7470 }
7471
7472 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7473
7474 void
7475 LeftClick (ClickType clickType, int xPix, int yPix)
7476 {
7477     int x, y;
7478     Boolean saveAnimate;
7479     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7480     char promoChoice = NULLCHAR;
7481     ChessSquare piece;
7482     static TimeMark lastClickTime, prevClickTime;
7483
7484     x = EventToSquare(xPix, BOARD_WIDTH);
7485     y = EventToSquare(yPix, BOARD_HEIGHT);
7486     if (!flipView && y >= 0) {
7487         y = BOARD_HEIGHT - 1 - y;
7488     }
7489     if (flipView && x >= 0) {
7490         x = BOARD_WIDTH - 1 - x;
7491     }
7492
7493     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7494         static int dummy;
7495         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7496         right = TRUE;
7497         return;
7498     }
7499
7500     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7501
7502     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7503
7504     if (clickType == Press) ErrorPopDown();
7505     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7506
7507     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7508         defaultPromoChoice = promoSweep;
7509         promoSweep = EmptySquare;   // terminate sweep
7510         promoDefaultAltered = TRUE;
7511         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7512     }
7513
7514     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7515         if(clickType == Release) return; // ignore upclick of click-click destination
7516         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7517         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7518         if(gameInfo.holdingsWidth &&
7519                 (WhiteOnMove(currentMove)
7520                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7521                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7522             // click in right holdings, for determining promotion piece
7523             ChessSquare p = boards[currentMove][y][x];
7524             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7525             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7526             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7527                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7528                 fromX = fromY = -1;
7529                 return;
7530             }
7531         }
7532         DrawPosition(FALSE, boards[currentMove]);
7533         return;
7534     }
7535
7536     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7537     if(clickType == Press
7538             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7539               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7540               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7541         return;
7542
7543     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7544         // could be static click on premove from-square: abort premove
7545         gotPremove = 0;
7546         ClearPremoveHighlights();
7547     }
7548
7549     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7550         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7551
7552     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7553         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7554                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7555         defaultPromoChoice = DefaultPromoChoice(side);
7556     }
7557
7558     autoQueen = appData.alwaysPromoteToQueen;
7559
7560     if (fromX == -1) {
7561       int originalY = y;
7562       gatingPiece = EmptySquare;
7563       if (clickType != Press) {
7564         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7565             DragPieceEnd(xPix, yPix); dragging = 0;
7566             DrawPosition(FALSE, NULL);
7567         }
7568         return;
7569       }
7570       doubleClick = FALSE;
7571       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7572         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7573       }
7574       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7575       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7576          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7577          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7578             /* First square */
7579             if (OKToStartUserMove(fromX, fromY)) {
7580                 second = 0;
7581                 ReportClick("lift", x, y);
7582                 MarkTargetSquares(0);
7583                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7584                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7585                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7586                     promoSweep = defaultPromoChoice;
7587                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7588                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7589                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7590                 }
7591                 if (appData.highlightDragging) {
7592                     SetHighlights(fromX, fromY, -1, -1);
7593                 } else {
7594                     ClearHighlights();
7595                 }
7596             } else fromX = fromY = -1;
7597             return;
7598         }
7599     }
7600
7601     /* fromX != -1 */
7602     if (clickType == Press && gameMode != EditPosition) {
7603         ChessSquare fromP;
7604         ChessSquare toP;
7605         int frc;
7606
7607         // ignore off-board to clicks
7608         if(y < 0 || x < 0) return;
7609
7610         /* Check if clicking again on the same color piece */
7611         fromP = boards[currentMove][fromY][fromX];
7612         toP = boards[currentMove][y][x];
7613         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7614         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7615             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7616            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7617              WhitePawn <= toP && toP <= WhiteKing &&
7618              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7619              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7620             (BlackPawn <= fromP && fromP <= BlackKing &&
7621              BlackPawn <= toP && toP <= BlackKing &&
7622              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7623              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7624             /* Clicked again on same color piece -- changed his mind */
7625             second = (x == fromX && y == fromY);
7626             killX = killY = -1;
7627             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7628                 second = FALSE; // first double-click rather than scond click
7629                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7630             }
7631             promoDefaultAltered = FALSE;
7632             MarkTargetSquares(1);
7633            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7634             if (appData.highlightDragging) {
7635                 SetHighlights(x, y, -1, -1);
7636             } else {
7637                 ClearHighlights();
7638             }
7639             if (OKToStartUserMove(x, y)) {
7640                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7641                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7642                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7643                  gatingPiece = boards[currentMove][fromY][fromX];
7644                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7645                 fromX = x;
7646                 fromY = y; dragging = 1;
7647                 if(!second) ReportClick("lift", x, y);
7648                 MarkTargetSquares(0);
7649                 DragPieceBegin(xPix, yPix, FALSE);
7650                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7651                     promoSweep = defaultPromoChoice;
7652                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7653                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7654                 }
7655             }
7656            }
7657            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7658            second = FALSE;
7659         }
7660         // ignore clicks on holdings
7661         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7662     }
7663
7664     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7665         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7666         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7667         return;
7668     }
7669
7670     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7671         DragPieceEnd(xPix, yPix); dragging = 0;
7672         if(clearFlag) {
7673             // a deferred attempt to click-click move an empty square on top of a piece
7674             boards[currentMove][y][x] = EmptySquare;
7675             ClearHighlights();
7676             DrawPosition(FALSE, boards[currentMove]);
7677             fromX = fromY = -1; clearFlag = 0;
7678             return;
7679         }
7680         if (appData.animateDragging) {
7681             /* Undo animation damage if any */
7682             DrawPosition(FALSE, NULL);
7683         }
7684         if (second) {
7685             /* Second up/down in same square; just abort move */
7686             second = 0;
7687             fromX = fromY = -1;
7688             gatingPiece = EmptySquare;
7689             MarkTargetSquares(1);
7690             ClearHighlights();
7691             gotPremove = 0;
7692             ClearPremoveHighlights();
7693         } else {
7694             /* First upclick in same square; start click-click mode */
7695             SetHighlights(x, y, -1, -1);
7696         }
7697         return;
7698     }
7699
7700     clearFlag = 0;
7701
7702     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7703        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7704         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7705         DisplayMessage(_("only marked squares are legal"),"");
7706         DrawPosition(TRUE, NULL);
7707         return; // ignore to-click
7708     }
7709
7710     /* we now have a different from- and (possibly off-board) to-square */
7711     /* Completed move */
7712     if(!sweepSelecting) {
7713         toX = x;
7714         toY = y;
7715     }
7716
7717     piece = boards[currentMove][fromY][fromX];
7718
7719     saveAnimate = appData.animate;
7720     if (clickType == Press) {
7721         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7722         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7723             // must be Edit Position mode with empty-square selected
7724             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7725             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7726             return;
7727         }
7728         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7729             return;
7730         }
7731         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7732             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7733         } else
7734         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7735         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7736           if(appData.sweepSelect) {
7737             promoSweep = defaultPromoChoice;
7738             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7739             selectFlag = 0; lastX = xPix; lastY = yPix;
7740             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7741             Sweep(0); // Pawn that is going to promote: preview promotion piece
7742             sweepSelecting = 1;
7743             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7744             MarkTargetSquares(1);
7745           }
7746           return; // promo popup appears on up-click
7747         }
7748         /* Finish clickclick move */
7749         if (appData.animate || appData.highlightLastMove) {
7750             SetHighlights(fromX, fromY, toX, toY);
7751         } else {
7752             ClearHighlights();
7753         }
7754     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7755         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7756         *promoRestrict = 0;
7757         if (appData.animate || appData.highlightLastMove) {
7758             SetHighlights(fromX, fromY, toX, toY);
7759         } else {
7760             ClearHighlights();
7761         }
7762     } else {
7763 #if 0
7764 // [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
7765         /* Finish drag move */
7766         if (appData.highlightLastMove) {
7767             SetHighlights(fromX, fromY, toX, toY);
7768         } else {
7769             ClearHighlights();
7770         }
7771 #endif
7772         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7773         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7774           dragging *= 2;            // flag button-less dragging if we are dragging
7775           MarkTargetSquares(1);
7776           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7777           else {
7778             kill2X = killX; kill2Y = killY;
7779             killX = x; killY = y;     //remeber this square as intermediate
7780             ReportClick("put", x, y); // and inform engine
7781             ReportClick("lift", x, y);
7782             MarkTargetSquares(0);
7783             return;
7784           }
7785         }
7786         DragPieceEnd(xPix, yPix); dragging = 0;
7787         /* Don't animate move and drag both */
7788         appData.animate = FALSE;
7789     }
7790
7791     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7792     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7793         ChessSquare piece = boards[currentMove][fromY][fromX];
7794         if(gameMode == EditPosition && piece != EmptySquare &&
7795            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7796             int n;
7797
7798             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7799                 n = PieceToNumber(piece - (int)BlackPawn);
7800                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7801                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7802                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7803             } else
7804             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7805                 n = PieceToNumber(piece);
7806                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7807                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7808                 boards[currentMove][n][BOARD_WIDTH-2]++;
7809             }
7810             boards[currentMove][fromY][fromX] = EmptySquare;
7811         }
7812         ClearHighlights();
7813         fromX = fromY = -1;
7814         MarkTargetSquares(1);
7815         DrawPosition(TRUE, boards[currentMove]);
7816         return;
7817     }
7818
7819     // off-board moves should not be highlighted
7820     if(x < 0 || y < 0) ClearHighlights();
7821     else ReportClick("put", x, y);
7822
7823     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7824
7825     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7826
7827     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7828         SetHighlights(fromX, fromY, toX, toY);
7829         MarkTargetSquares(1);
7830         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7831             // [HGM] super: promotion to captured piece selected from holdings
7832             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7833             promotionChoice = TRUE;
7834             // kludge follows to temporarily execute move on display, without promoting yet
7835             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7836             boards[currentMove][toY][toX] = p;
7837             DrawPosition(FALSE, boards[currentMove]);
7838             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7839             boards[currentMove][toY][toX] = q;
7840             DisplayMessage("Click in holdings to choose piece", "");
7841             return;
7842         }
7843         PromotionPopUp(promoChoice);
7844     } else {
7845         int oldMove = currentMove;
7846         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7847         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7848         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7849         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7850            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7851             DrawPosition(TRUE, boards[currentMove]);
7852         MarkTargetSquares(1);
7853         fromX = fromY = -1;
7854     }
7855     appData.animate = saveAnimate;
7856     if (appData.animate || appData.animateDragging) {
7857         /* Undo animation damage if needed */
7858         DrawPosition(FALSE, NULL);
7859     }
7860 }
7861
7862 int
7863 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7864 {   // front-end-free part taken out of PieceMenuPopup
7865     int whichMenu; int xSqr, ySqr;
7866
7867     if(seekGraphUp) { // [HGM] seekgraph
7868         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7869         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7870         return -2;
7871     }
7872
7873     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7874          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7875         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7876         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7877         if(action == Press)   {
7878             originalFlip = flipView;
7879             flipView = !flipView; // temporarily flip board to see game from partners perspective
7880             DrawPosition(TRUE, partnerBoard);
7881             DisplayMessage(partnerStatus, "");
7882             partnerUp = TRUE;
7883         } else if(action == Release) {
7884             flipView = originalFlip;
7885             DrawPosition(TRUE, boards[currentMove]);
7886             partnerUp = FALSE;
7887         }
7888         return -2;
7889     }
7890
7891     xSqr = EventToSquare(x, BOARD_WIDTH);
7892     ySqr = EventToSquare(y, BOARD_HEIGHT);
7893     if (action == Release) {
7894         if(pieceSweep != EmptySquare) {
7895             EditPositionMenuEvent(pieceSweep, toX, toY);
7896             pieceSweep = EmptySquare;
7897         } else UnLoadPV(); // [HGM] pv
7898     }
7899     if (action != Press) return -2; // return code to be ignored
7900     switch (gameMode) {
7901       case IcsExamining:
7902         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7903       case EditPosition:
7904         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7905         if (xSqr < 0 || ySqr < 0) return -1;
7906         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7907         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7908         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7909         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7910         NextPiece(0);
7911         return 2; // grab
7912       case IcsObserving:
7913         if(!appData.icsEngineAnalyze) return -1;
7914       case IcsPlayingWhite:
7915       case IcsPlayingBlack:
7916         if(!appData.zippyPlay) goto noZip;
7917       case AnalyzeMode:
7918       case AnalyzeFile:
7919       case MachinePlaysWhite:
7920       case MachinePlaysBlack:
7921       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7922         if (!appData.dropMenu) {
7923           LoadPV(x, y);
7924           return 2; // flag front-end to grab mouse events
7925         }
7926         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7927            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7928       case EditGame:
7929       noZip:
7930         if (xSqr < 0 || ySqr < 0) return -1;
7931         if (!appData.dropMenu || appData.testLegality &&
7932             gameInfo.variant != VariantBughouse &&
7933             gameInfo.variant != VariantCrazyhouse) return -1;
7934         whichMenu = 1; // drop menu
7935         break;
7936       default:
7937         return -1;
7938     }
7939
7940     if (((*fromX = xSqr) < 0) ||
7941         ((*fromY = ySqr) < 0)) {
7942         *fromX = *fromY = -1;
7943         return -1;
7944     }
7945     if (flipView)
7946       *fromX = BOARD_WIDTH - 1 - *fromX;
7947     else
7948       *fromY = BOARD_HEIGHT - 1 - *fromY;
7949
7950     return whichMenu;
7951 }
7952
7953 void
7954 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7955 {
7956 //    char * hint = lastHint;
7957     FrontEndProgramStats stats;
7958
7959     stats.which = cps == &first ? 0 : 1;
7960     stats.depth = cpstats->depth;
7961     stats.nodes = cpstats->nodes;
7962     stats.score = cpstats->score;
7963     stats.time = cpstats->time;
7964     stats.pv = cpstats->movelist;
7965     stats.hint = lastHint;
7966     stats.an_move_index = 0;
7967     stats.an_move_count = 0;
7968
7969     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7970         stats.hint = cpstats->move_name;
7971         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7972         stats.an_move_count = cpstats->nr_moves;
7973     }
7974
7975     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
7976
7977     SetProgramStats( &stats );
7978 }
7979
7980 void
7981 ClearEngineOutputPane (int which)
7982 {
7983     static FrontEndProgramStats dummyStats;
7984     dummyStats.which = which;
7985     dummyStats.pv = "#";
7986     SetProgramStats( &dummyStats );
7987 }
7988
7989 #define MAXPLAYERS 500
7990
7991 char *
7992 TourneyStandings (int display)
7993 {
7994     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7995     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7996     char result, *p, *names[MAXPLAYERS];
7997
7998     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7999         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8000     names[0] = p = strdup(appData.participants);
8001     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8002
8003     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8004
8005     while(result = appData.results[nr]) {
8006         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8007         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8008         wScore = bScore = 0;
8009         switch(result) {
8010           case '+': wScore = 2; break;
8011           case '-': bScore = 2; break;
8012           case '=': wScore = bScore = 1; break;
8013           case ' ':
8014           case '*': return strdup("busy"); // tourney not finished
8015         }
8016         score[w] += wScore;
8017         score[b] += bScore;
8018         games[w]++;
8019         games[b]++;
8020         nr++;
8021     }
8022     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8023     for(w=0; w<nPlayers; w++) {
8024         bScore = -1;
8025         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8026         ranking[w] = b; points[w] = bScore; score[b] = -2;
8027     }
8028     p = malloc(nPlayers*34+1);
8029     for(w=0; w<nPlayers && w<display; w++)
8030         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8031     free(names[0]);
8032     return p;
8033 }
8034
8035 void
8036 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8037 {       // count all piece types
8038         int p, f, r;
8039         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8040         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8041         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8042                 p = board[r][f];
8043                 pCnt[p]++;
8044                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8045                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8046                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8047                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8048                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8049                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8050         }
8051 }
8052
8053 int
8054 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8055 {
8056         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8057         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8058
8059         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8060         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8061         if(myPawns == 2 && nMine == 3) // KPP
8062             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8063         if(myPawns == 1 && nMine == 2) // KP
8064             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8065         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8066             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8067         if(myPawns) return FALSE;
8068         if(pCnt[WhiteRook+side])
8069             return pCnt[BlackRook-side] ||
8070                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8071                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8072                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8073         if(pCnt[WhiteCannon+side]) {
8074             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8075             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8076         }
8077         if(pCnt[WhiteKnight+side])
8078             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8079         return FALSE;
8080 }
8081
8082 int
8083 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8084 {
8085         VariantClass v = gameInfo.variant;
8086
8087         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8088         if(v == VariantShatranj) return TRUE; // always winnable through baring
8089         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8090         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8091
8092         if(v == VariantXiangqi) {
8093                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8094
8095                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8096                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8097                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8098                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8099                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8100                 if(stale) // we have at least one last-rank P plus perhaps C
8101                     return majors // KPKX
8102                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8103                 else // KCA*E*
8104                     return pCnt[WhiteFerz+side] // KCAK
8105                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8106                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8107                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8108
8109         } else if(v == VariantKnightmate) {
8110                 if(nMine == 1) return FALSE;
8111                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8112         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8113                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8114
8115                 if(nMine == 1) return FALSE; // bare King
8116                 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
8117                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8118                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8119                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8120                 if(pCnt[WhiteKnight+side])
8121                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8122                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8123                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8124                 if(nBishops)
8125                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8126                 if(pCnt[WhiteAlfil+side])
8127                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8128                 if(pCnt[WhiteWazir+side])
8129                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8130         }
8131
8132         return TRUE;
8133 }
8134
8135 int
8136 CompareWithRights (Board b1, Board b2)
8137 {
8138     int rights = 0;
8139     if(!CompareBoards(b1, b2)) return FALSE;
8140     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8141     /* compare castling rights */
8142     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8143            rights++; /* King lost rights, while rook still had them */
8144     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8145         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8146            rights++; /* but at least one rook lost them */
8147     }
8148     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8149            rights++;
8150     if( b1[CASTLING][5] != NoRights ) {
8151         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8152            rights++;
8153     }
8154     return rights == 0;
8155 }
8156
8157 int
8158 Adjudicate (ChessProgramState *cps)
8159 {       // [HGM] some adjudications useful with buggy engines
8160         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8161         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8162         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8163         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8164         int k, drop, count = 0; static int bare = 1;
8165         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8166         Boolean canAdjudicate = !appData.icsActive;
8167
8168         // most tests only when we understand the game, i.e. legality-checking on
8169             if( appData.testLegality )
8170             {   /* [HGM] Some more adjudications for obstinate engines */
8171                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8172                 static int moveCount = 6;
8173                 ChessMove result;
8174                 char *reason = NULL;
8175
8176                 /* Count what is on board. */
8177                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8178
8179                 /* Some material-based adjudications that have to be made before stalemate test */
8180                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8181                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8182                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8183                      if(canAdjudicate && appData.checkMates) {
8184                          if(engineOpponent)
8185                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8186                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8187                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8188                          return 1;
8189                      }
8190                 }
8191
8192                 /* Bare King in Shatranj (loses) or Losers (wins) */
8193                 if( nrW == 1 || nrB == 1) {
8194                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8195                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8196                      if(canAdjudicate && appData.checkMates) {
8197                          if(engineOpponent)
8198                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8199                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8200                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8201                          return 1;
8202                      }
8203                   } else
8204                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8205                   {    /* bare King */
8206                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8207                         if(canAdjudicate && appData.checkMates) {
8208                             /* but only adjudicate if adjudication enabled */
8209                             if(engineOpponent)
8210                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8211                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8212                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8213                             return 1;
8214                         }
8215                   }
8216                 } else bare = 1;
8217
8218
8219             // don't wait for engine to announce game end if we can judge ourselves
8220             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8221               case MT_CHECK:
8222                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8223                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8224                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8225                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8226                             checkCnt++;
8227                         if(checkCnt >= 2) {
8228                             reason = "Xboard adjudication: 3rd check";
8229                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8230                             break;
8231                         }
8232                     }
8233                 }
8234               case MT_NONE:
8235               default:
8236                 break;
8237               case MT_STEALMATE:
8238               case MT_STALEMATE:
8239               case MT_STAINMATE:
8240                 reason = "Xboard adjudication: Stalemate";
8241                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8242                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8243                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8244                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8245                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8246                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8247                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8248                                                                         EP_CHECKMATE : EP_WINS);
8249                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8250                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8251                 }
8252                 break;
8253               case MT_CHECKMATE:
8254                 reason = "Xboard adjudication: Checkmate";
8255                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8256                 if(gameInfo.variant == VariantShogi) {
8257                     if(forwardMostMove > backwardMostMove
8258                        && moveList[forwardMostMove-1][1] == '@'
8259                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8260                         reason = "XBoard adjudication: pawn-drop mate";
8261                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8262                     }
8263                 }
8264                 break;
8265             }
8266
8267                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8268                     case EP_STALEMATE:
8269                         result = GameIsDrawn; break;
8270                     case EP_CHECKMATE:
8271                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8272                     case EP_WINS:
8273                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8274                     default:
8275                         result = EndOfFile;
8276                 }
8277                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8278                     if(engineOpponent)
8279                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8280                     GameEnds( result, reason, GE_XBOARD );
8281                     return 1;
8282                 }
8283
8284                 /* Next absolutely insufficient mating material. */
8285                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8286                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8287                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8288
8289                      /* always flag draws, for judging claims */
8290                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8291
8292                      if(canAdjudicate && appData.materialDraws) {
8293                          /* but only adjudicate them if adjudication enabled */
8294                          if(engineOpponent) {
8295                            SendToProgram("force\n", engineOpponent); // suppress reply
8296                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8297                          }
8298                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8299                          return 1;
8300                      }
8301                 }
8302
8303                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8304                 if(gameInfo.variant == VariantXiangqi ?
8305                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8306                  : nrW + nrB == 4 &&
8307                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8308                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8309                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8310                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8311                    ) ) {
8312                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8313                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8314                           if(engineOpponent) {
8315                             SendToProgram("force\n", engineOpponent); // suppress reply
8316                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8317                           }
8318                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8319                           return 1;
8320                      }
8321                 } else moveCount = 6;
8322             }
8323
8324         // Repetition draws and 50-move rule can be applied independently of legality testing
8325
8326                 /* Check for rep-draws */
8327                 count = 0;
8328                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8329                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8330                 for(k = forwardMostMove-2;
8331                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8332                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8333                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8334                     k-=2)
8335                 {   int rights=0;
8336                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8337                         /* compare castling rights */
8338                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8339                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8340                                 rights++; /* King lost rights, while rook still had them */
8341                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8342                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8343                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8344                                    rights++; /* but at least one rook lost them */
8345                         }
8346                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8347                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8348                                 rights++;
8349                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8350                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8351                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8352                                    rights++;
8353                         }
8354                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8355                             && appData.drawRepeats > 1) {
8356                              /* adjudicate after user-specified nr of repeats */
8357                              int result = GameIsDrawn;
8358                              char *details = "XBoard adjudication: repetition draw";
8359                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8360                                 // [HGM] xiangqi: check for forbidden perpetuals
8361                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8362                                 for(m=forwardMostMove; m>k; m-=2) {
8363                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8364                                         ourPerpetual = 0; // the current mover did not always check
8365                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8366                                         hisPerpetual = 0; // the opponent did not always check
8367                                 }
8368                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8369                                                                         ourPerpetual, hisPerpetual);
8370                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8371                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8372                                     details = "Xboard adjudication: perpetual checking";
8373                                 } else
8374                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8375                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8376                                 } else
8377                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8378                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8379                                         result = BlackWins;
8380                                         details = "Xboard adjudication: repetition";
8381                                     }
8382                                 } else // it must be XQ
8383                                 // Now check for perpetual chases
8384                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8385                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8386                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8387                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8388                                         static char resdet[MSG_SIZ];
8389                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8390                                         details = resdet;
8391                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8392                                     } else
8393                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8394                                         break; // Abort repetition-checking loop.
8395                                 }
8396                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8397                              }
8398                              if(engineOpponent) {
8399                                SendToProgram("force\n", engineOpponent); // suppress reply
8400                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8401                              }
8402                              GameEnds( result, details, GE_XBOARD );
8403                              return 1;
8404                         }
8405                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8406                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8407                     }
8408                 }
8409
8410                 /* Now we test for 50-move draws. Determine ply count */
8411                 count = forwardMostMove;
8412                 /* look for last irreversble move */
8413                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8414                     count--;
8415                 /* if we hit starting position, add initial plies */
8416                 if( count == backwardMostMove )
8417                     count -= initialRulePlies;
8418                 count = forwardMostMove - count;
8419                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8420                         // adjust reversible move counter for checks in Xiangqi
8421                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8422                         if(i < backwardMostMove) i = backwardMostMove;
8423                         while(i <= forwardMostMove) {
8424                                 lastCheck = inCheck; // check evasion does not count
8425                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8426                                 if(inCheck || lastCheck) count--; // check does not count
8427                                 i++;
8428                         }
8429                 }
8430                 if( count >= 100)
8431                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8432                          /* this is used to judge if draw claims are legal */
8433                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8434                          if(engineOpponent) {
8435                            SendToProgram("force\n", engineOpponent); // suppress reply
8436                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8437                          }
8438                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8439                          return 1;
8440                 }
8441
8442                 /* if draw offer is pending, treat it as a draw claim
8443                  * when draw condition present, to allow engines a way to
8444                  * claim draws before making their move to avoid a race
8445                  * condition occurring after their move
8446                  */
8447                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8448                          char *p = NULL;
8449                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8450                              p = "Draw claim: 50-move rule";
8451                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8452                              p = "Draw claim: 3-fold repetition";
8453                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8454                              p = "Draw claim: insufficient mating material";
8455                          if( p != NULL && canAdjudicate) {
8456                              if(engineOpponent) {
8457                                SendToProgram("force\n", engineOpponent); // suppress reply
8458                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8459                              }
8460                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8461                              return 1;
8462                          }
8463                 }
8464
8465                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8466                     if(engineOpponent) {
8467                       SendToProgram("force\n", engineOpponent); // suppress reply
8468                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8469                     }
8470                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8471                     return 1;
8472                 }
8473         return 0;
8474 }
8475
8476 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8477 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8478 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8479
8480 static int
8481 BitbaseProbe ()
8482 {
8483     int pieces[10], squares[10], cnt=0, r, f, res;
8484     static int loaded;
8485     static PPROBE_EGBB probeBB;
8486     if(!appData.testLegality) return 10;
8487     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8488     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8489     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8490     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8491         ChessSquare piece = boards[forwardMostMove][r][f];
8492         int black = (piece >= BlackPawn);
8493         int type = piece - black*BlackPawn;
8494         if(piece == EmptySquare) continue;
8495         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8496         if(type == WhiteKing) type = WhiteQueen + 1;
8497         type = egbbCode[type];
8498         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8499         pieces[cnt] = type + black*6;
8500         if(++cnt > 5) return 11;
8501     }
8502     pieces[cnt] = squares[cnt] = 0;
8503     // probe EGBB
8504     if(loaded == 2) return 13; // loading failed before
8505     if(loaded == 0) {
8506         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8507         HMODULE lib;
8508         PLOAD_EGBB loadBB;
8509         loaded = 2; // prepare for failure
8510         if(!path) return 13; // no egbb installed
8511         strncpy(buf, path + 8, MSG_SIZ);
8512         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8513         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8514         lib = LoadLibrary(buf);
8515         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8516         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8517         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8518         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8519         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8520         loaded = 1; // success!
8521     }
8522     res = probeBB(forwardMostMove & 1, pieces, squares);
8523     return res > 0 ? 1 : res < 0 ? -1 : 0;
8524 }
8525
8526 char *
8527 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8528 {   // [HGM] book: this routine intercepts moves to simulate book replies
8529     char *bookHit = NULL;
8530
8531     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8532         char buf[MSG_SIZ];
8533         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8534         SendToProgram(buf, cps);
8535     }
8536     //first determine if the incoming move brings opponent into his book
8537     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8538         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8539     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8540     if(bookHit != NULL && !cps->bookSuspend) {
8541         // make sure opponent is not going to reply after receiving move to book position
8542         SendToProgram("force\n", cps);
8543         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8544     }
8545     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8546     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8547     // now arrange restart after book miss
8548     if(bookHit) {
8549         // after a book hit we never send 'go', and the code after the call to this routine
8550         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8551         char buf[MSG_SIZ], *move = bookHit;
8552         if(cps->useSAN) {
8553             int fromX, fromY, toX, toY;
8554             char promoChar;
8555             ChessMove moveType;
8556             move = buf + 30;
8557             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8558                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8559                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8560                                     PosFlags(forwardMostMove),
8561                                     fromY, fromX, toY, toX, promoChar, move);
8562             } else {
8563                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8564                 bookHit = NULL;
8565             }
8566         }
8567         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8568         SendToProgram(buf, cps);
8569         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8570     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8571         SendToProgram("go\n", cps);
8572         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8573     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8574         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8575             SendToProgram("go\n", cps);
8576         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8577     }
8578     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8579 }
8580
8581 int
8582 LoadError (char *errmess, ChessProgramState *cps)
8583 {   // unloads engine and switches back to -ncp mode if it was first
8584     if(cps->initDone) return FALSE;
8585     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8586     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8587     cps->pr = NoProc;
8588     if(cps == &first) {
8589         appData.noChessProgram = TRUE;
8590         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8591         gameMode = BeginningOfGame; ModeHighlight();
8592         SetNCPMode();
8593     }
8594     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8595     DisplayMessage("", ""); // erase waiting message
8596     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8597     return TRUE;
8598 }
8599
8600 char *savedMessage;
8601 ChessProgramState *savedState;
8602 void
8603 DeferredBookMove (void)
8604 {
8605         if(savedState->lastPing != savedState->lastPong)
8606                     ScheduleDelayedEvent(DeferredBookMove, 10);
8607         else
8608         HandleMachineMove(savedMessage, savedState);
8609 }
8610
8611 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8612 static ChessProgramState *stalledEngine;
8613 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8614
8615 void
8616 HandleMachineMove (char *message, ChessProgramState *cps)
8617 {
8618     static char firstLeg[20];
8619     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8620     char realname[MSG_SIZ];
8621     int fromX, fromY, toX, toY;
8622     ChessMove moveType;
8623     char promoChar, roar;
8624     char *p, *pv=buf1;
8625     int machineWhite, oldError;
8626     char *bookHit;
8627
8628     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8629         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8630         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8631             DisplayError(_("Invalid pairing from pairing engine"), 0);
8632             return;
8633         }
8634         pairingReceived = 1;
8635         NextMatchGame();
8636         return; // Skim the pairing messages here.
8637     }
8638
8639     oldError = cps->userError; cps->userError = 0;
8640
8641 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8642     /*
8643      * Kludge to ignore BEL characters
8644      */
8645     while (*message == '\007') message++;
8646
8647     /*
8648      * [HGM] engine debug message: ignore lines starting with '#' character
8649      */
8650     if(cps->debug && *message == '#') return;
8651
8652     /*
8653      * Look for book output
8654      */
8655     if (cps == &first && bookRequested) {
8656         if (message[0] == '\t' || message[0] == ' ') {
8657             /* Part of the book output is here; append it */
8658             strcat(bookOutput, message);
8659             strcat(bookOutput, "  \n");
8660             return;
8661         } else if (bookOutput[0] != NULLCHAR) {
8662             /* All of book output has arrived; display it */
8663             char *p = bookOutput;
8664             while (*p != NULLCHAR) {
8665                 if (*p == '\t') *p = ' ';
8666                 p++;
8667             }
8668             DisplayInformation(bookOutput);
8669             bookRequested = FALSE;
8670             /* Fall through to parse the current output */
8671         }
8672     }
8673
8674     /*
8675      * Look for machine move.
8676      */
8677     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8678         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8679     {
8680         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8681             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8682             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8683             stalledEngine = cps;
8684             if(appData.ponderNextMove) { // bring opponent out of ponder
8685                 if(gameMode == TwoMachinesPlay) {
8686                     if(cps->other->pause)
8687                         PauseEngine(cps->other);
8688                     else
8689                         SendToProgram("easy\n", cps->other);
8690                 }
8691             }
8692             StopClocks();
8693             return;
8694         }
8695
8696       if(cps->usePing) {
8697
8698         /* This method is only useful on engines that support ping */
8699         if(abortEngineThink) {
8700             if (appData.debugMode) {
8701                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8702             }
8703             SendToProgram("undo\n", cps);
8704             return;
8705         }
8706
8707         if (cps->lastPing != cps->lastPong) {
8708             /* Extra move from before last new; ignore */
8709             if (appData.debugMode) {
8710                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8711             }
8712           return;
8713         }
8714
8715       } else {
8716
8717         switch (gameMode) {
8718           case BeginningOfGame:
8719             /* Extra move from before last reset; ignore */
8720             if (appData.debugMode) {
8721                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8722             }
8723             return;
8724
8725           case EndOfGame:
8726           case IcsIdle:
8727           default:
8728             /* Extra move after we tried to stop.  The mode test is
8729                not a reliable way of detecting this problem, but it's
8730                the best we can do on engines that don't support ping.
8731             */
8732             if (appData.debugMode) {
8733                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8734                         cps->which, gameMode);
8735             }
8736             SendToProgram("undo\n", cps);
8737             return;
8738
8739           case MachinePlaysWhite:
8740           case IcsPlayingWhite:
8741             machineWhite = TRUE;
8742             break;
8743
8744           case MachinePlaysBlack:
8745           case IcsPlayingBlack:
8746             machineWhite = FALSE;
8747             break;
8748
8749           case TwoMachinesPlay:
8750             machineWhite = (cps->twoMachinesColor[0] == 'w');
8751             break;
8752         }
8753         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8754             if (appData.debugMode) {
8755                 fprintf(debugFP,
8756                         "Ignoring move out of turn by %s, gameMode %d"
8757                         ", forwardMost %d\n",
8758                         cps->which, gameMode, forwardMostMove);
8759             }
8760             return;
8761         }
8762       }
8763
8764         if(cps->alphaRank) AlphaRank(machineMove, 4);
8765
8766         // [HGM] lion: (some very limited) support for Alien protocol
8767         killX = killY = kill2X = kill2Y = -1;
8768         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8769             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8770             return;
8771         }
8772         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8773             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8774             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8775         }
8776         if(firstLeg[0]) { // there was a previous leg;
8777             // only support case where same piece makes two step
8778             char buf[20], *p = machineMove+1, *q = buf+1, f;
8779             safeStrCpy(buf, machineMove, 20);
8780             while(isdigit(*q)) q++; // find start of to-square
8781             safeStrCpy(machineMove, firstLeg, 20);
8782             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8783             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8784             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8785             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8786             firstLeg[0] = NULLCHAR;
8787         }
8788
8789         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8790                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8791             /* Machine move could not be parsed; ignore it. */
8792           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8793                     machineMove, _(cps->which));
8794             DisplayMoveError(buf1);
8795             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8796                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8797             if (gameMode == TwoMachinesPlay) {
8798               GameEnds(machineWhite ? BlackWins : WhiteWins,
8799                        buf1, GE_XBOARD);
8800             }
8801             return;
8802         }
8803
8804         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8805         /* So we have to redo legality test with true e.p. status here,  */
8806         /* to make sure an illegal e.p. capture does not slip through,   */
8807         /* to cause a forfeit on a justified illegal-move complaint      */
8808         /* of the opponent.                                              */
8809         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8810            ChessMove moveType;
8811            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8812                              fromY, fromX, toY, toX, promoChar);
8813             if(moveType == IllegalMove) {
8814               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8815                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8816                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8817                            buf1, GE_XBOARD);
8818                 return;
8819            } else if(!appData.fischerCastling)
8820            /* [HGM] Kludge to handle engines that send FRC-style castling
8821               when they shouldn't (like TSCP-Gothic) */
8822            switch(moveType) {
8823              case WhiteASideCastleFR:
8824              case BlackASideCastleFR:
8825                toX+=2;
8826                currentMoveString[2]++;
8827                break;
8828              case WhiteHSideCastleFR:
8829              case BlackHSideCastleFR:
8830                toX--;
8831                currentMoveString[2]--;
8832                break;
8833              default: ; // nothing to do, but suppresses warning of pedantic compilers
8834            }
8835         }
8836         hintRequested = FALSE;
8837         lastHint[0] = NULLCHAR;
8838         bookRequested = FALSE;
8839         /* Program may be pondering now */
8840         cps->maybeThinking = TRUE;
8841         if (cps->sendTime == 2) cps->sendTime = 1;
8842         if (cps->offeredDraw) cps->offeredDraw--;
8843
8844         /* [AS] Save move info*/
8845         pvInfoList[ forwardMostMove ].score = programStats.score;
8846         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8847         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8848
8849         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8850
8851         /* Test suites abort the 'game' after one move */
8852         if(*appData.finger) {
8853            static FILE *f;
8854            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8855            if(!f) f = fopen(appData.finger, "w");
8856            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8857            else { DisplayFatalError("Bad output file", errno, 0); return; }
8858            free(fen);
8859            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8860         }
8861         if(appData.epd) {
8862            if(solvingTime >= 0) {
8863               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8864               totalTime += solvingTime; first.matchWins++;
8865            } else {
8866               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8867               second.matchWins++;
8868            }
8869            OutputKibitz(2, buf1);
8870            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8871         }
8872
8873         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8874         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8875             int count = 0;
8876
8877             while( count < adjudicateLossPlies ) {
8878                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8879
8880                 if( count & 1 ) {
8881                     score = -score; /* Flip score for winning side */
8882                 }
8883
8884                 if( score > appData.adjudicateLossThreshold ) {
8885                     break;
8886                 }
8887
8888                 count++;
8889             }
8890
8891             if( count >= adjudicateLossPlies ) {
8892                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8893
8894                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8895                     "Xboard adjudication",
8896                     GE_XBOARD );
8897
8898                 return;
8899             }
8900         }
8901
8902         if(Adjudicate(cps)) {
8903             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8904             return; // [HGM] adjudicate: for all automatic game ends
8905         }
8906
8907 #if ZIPPY
8908         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8909             first.initDone) {
8910           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8911                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8912                 SendToICS("draw ");
8913                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8914           }
8915           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8916           ics_user_moved = 1;
8917           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8918                 char buf[3*MSG_SIZ];
8919
8920                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8921                         programStats.score / 100.,
8922                         programStats.depth,
8923                         programStats.time / 100.,
8924                         (unsigned int)programStats.nodes,
8925                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8926                         programStats.movelist);
8927                 SendToICS(buf);
8928           }
8929         }
8930 #endif
8931
8932         /* [AS] Clear stats for next move */
8933         ClearProgramStats();
8934         thinkOutput[0] = NULLCHAR;
8935         hiddenThinkOutputState = 0;
8936
8937         bookHit = NULL;
8938         if (gameMode == TwoMachinesPlay) {
8939             /* [HGM] relaying draw offers moved to after reception of move */
8940             /* and interpreting offer as claim if it brings draw condition */
8941             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8942                 SendToProgram("draw\n", cps->other);
8943             }
8944             if (cps->other->sendTime) {
8945                 SendTimeRemaining(cps->other,
8946                                   cps->other->twoMachinesColor[0] == 'w');
8947             }
8948             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8949             if (firstMove && !bookHit) {
8950                 firstMove = FALSE;
8951                 if (cps->other->useColors) {
8952                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8953                 }
8954                 SendToProgram("go\n", cps->other);
8955             }
8956             cps->other->maybeThinking = TRUE;
8957         }
8958
8959         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8960
8961         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8962
8963         if (!pausing && appData.ringBellAfterMoves) {
8964             if(!roar) RingBell();
8965         }
8966
8967         /*
8968          * Reenable menu items that were disabled while
8969          * machine was thinking
8970          */
8971         if (gameMode != TwoMachinesPlay)
8972             SetUserThinkingEnables();
8973
8974         // [HGM] book: after book hit opponent has received move and is now in force mode
8975         // force the book reply into it, and then fake that it outputted this move by jumping
8976         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8977         if(bookHit) {
8978                 static char bookMove[MSG_SIZ]; // a bit generous?
8979
8980                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8981                 strcat(bookMove, bookHit);
8982                 message = bookMove;
8983                 cps = cps->other;
8984                 programStats.nodes = programStats.depth = programStats.time =
8985                 programStats.score = programStats.got_only_move = 0;
8986                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8987
8988                 if(cps->lastPing != cps->lastPong) {
8989                     savedMessage = message; // args for deferred call
8990                     savedState = cps;
8991                     ScheduleDelayedEvent(DeferredBookMove, 10);
8992                     return;
8993                 }
8994                 goto FakeBookMove;
8995         }
8996
8997         return;
8998     }
8999
9000     /* Set special modes for chess engines.  Later something general
9001      *  could be added here; for now there is just one kludge feature,
9002      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9003      *  when "xboard" is given as an interactive command.
9004      */
9005     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9006         cps->useSigint = FALSE;
9007         cps->useSigterm = FALSE;
9008     }
9009     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9010       ParseFeatures(message+8, cps);
9011       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9012     }
9013
9014     if (!strncmp(message, "setup ", 6) && 
9015         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9016           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9017                                         ) { // [HGM] allow first engine to define opening position
9018       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9019       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9020       *buf = NULLCHAR;
9021       if(sscanf(message, "setup (%s", buf) == 1) {
9022         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9023         ASSIGN(appData.pieceToCharTable, buf);
9024       }
9025       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9026       if(dummy >= 3) {
9027         while(message[s] && message[s++] != ' ');
9028         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9029            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9030             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9031             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9032           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9033           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9034           startedFromSetupPosition = FALSE;
9035         }
9036       }
9037       if(startedFromSetupPosition) return;
9038       ParseFEN(boards[0], &dummy, message+s, FALSE);
9039       DrawPosition(TRUE, boards[0]);
9040       CopyBoard(initialPosition, boards[0]);
9041       startedFromSetupPosition = TRUE;
9042       return;
9043     }
9044     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9045       ChessSquare piece = WhitePawn;
9046       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9047       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9048       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9049       piece += CharToPiece(ID & 255) - WhitePawn;
9050       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9051       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9052       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9053       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9054       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9055       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9056                                                && gameInfo.variant != VariantGreat
9057                                                && gameInfo.variant != VariantFairy    ) return;
9058       if(piece < EmptySquare) {
9059         pieceDefs = TRUE;
9060         ASSIGN(pieceDesc[piece], buf1);
9061         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9062       }
9063       return;
9064     }
9065     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9066       promoSweep = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9067       Sweep(0);
9068       return;
9069     }
9070     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9071      * want this, I was asked to put it in, and obliged.
9072      */
9073     if (!strncmp(message, "setboard ", 9)) {
9074         Board initial_position;
9075
9076         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9077
9078         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9079             DisplayError(_("Bad FEN received from engine"), 0);
9080             return ;
9081         } else {
9082            Reset(TRUE, FALSE);
9083            CopyBoard(boards[0], initial_position);
9084            initialRulePlies = FENrulePlies;
9085            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9086            else gameMode = MachinePlaysBlack;
9087            DrawPosition(FALSE, boards[currentMove]);
9088         }
9089         return;
9090     }
9091
9092     /*
9093      * Look for communication commands
9094      */
9095     if (!strncmp(message, "telluser ", 9)) {
9096         if(message[9] == '\\' && message[10] == '\\')
9097             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9098         PlayTellSound();
9099         DisplayNote(message + 9);
9100         return;
9101     }
9102     if (!strncmp(message, "tellusererror ", 14)) {
9103         cps->userError = 1;
9104         if(message[14] == '\\' && message[15] == '\\')
9105             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9106         PlayTellSound();
9107         DisplayError(message + 14, 0);
9108         return;
9109     }
9110     if (!strncmp(message, "tellopponent ", 13)) {
9111       if (appData.icsActive) {
9112         if (loggedOn) {
9113           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9114           SendToICS(buf1);
9115         }
9116       } else {
9117         DisplayNote(message + 13);
9118       }
9119       return;
9120     }
9121     if (!strncmp(message, "tellothers ", 11)) {
9122       if (appData.icsActive) {
9123         if (loggedOn) {
9124           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9125           SendToICS(buf1);
9126         }
9127       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9128       return;
9129     }
9130     if (!strncmp(message, "tellall ", 8)) {
9131       if (appData.icsActive) {
9132         if (loggedOn) {
9133           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9134           SendToICS(buf1);
9135         }
9136       } else {
9137         DisplayNote(message + 8);
9138       }
9139       return;
9140     }
9141     if (strncmp(message, "warning", 7) == 0) {
9142         /* Undocumented feature, use tellusererror in new code */
9143         DisplayError(message, 0);
9144         return;
9145     }
9146     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9147         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9148         strcat(realname, " query");
9149         AskQuestion(realname, buf2, buf1, cps->pr);
9150         return;
9151     }
9152     /* Commands from the engine directly to ICS.  We don't allow these to be
9153      *  sent until we are logged on. Crafty kibitzes have been known to
9154      *  interfere with the login process.
9155      */
9156     if (loggedOn) {
9157         if (!strncmp(message, "tellics ", 8)) {
9158             SendToICS(message + 8);
9159             SendToICS("\n");
9160             return;
9161         }
9162         if (!strncmp(message, "tellicsnoalias ", 15)) {
9163             SendToICS(ics_prefix);
9164             SendToICS(message + 15);
9165             SendToICS("\n");
9166             return;
9167         }
9168         /* The following are for backward compatibility only */
9169         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9170             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9171             SendToICS(ics_prefix);
9172             SendToICS(message);
9173             SendToICS("\n");
9174             return;
9175         }
9176     }
9177     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9178         if(initPing == cps->lastPong) {
9179             if(gameInfo.variant == VariantUnknown) {
9180                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9181                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9182                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9183             }
9184             initPing = -1;
9185         }
9186         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9187             abortEngineThink = FALSE;
9188             DisplayMessage("", "");
9189             ThawUI();
9190         }
9191         return;
9192     }
9193     if(!strncmp(message, "highlight ", 10)) {
9194         if(appData.testLegality && !*engineVariant && appData.markers) return;
9195         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9196         return;
9197     }
9198     if(!strncmp(message, "click ", 6)) {
9199         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9200         if(appData.testLegality || !appData.oneClick) return;
9201         sscanf(message+6, "%c%d%c", &f, &y, &c);
9202         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9203         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9204         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9205         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9206         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9207         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9208             LeftClick(Release, lastLeftX, lastLeftY);
9209         controlKey  = (c == ',');
9210         LeftClick(Press, x, y);
9211         LeftClick(Release, x, y);
9212         first.highlight = f;
9213         return;
9214     }
9215     /*
9216      * If the move is illegal, cancel it and redraw the board.
9217      * Also deal with other error cases.  Matching is rather loose
9218      * here to accommodate engines written before the spec.
9219      */
9220     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9221         strncmp(message, "Error", 5) == 0) {
9222         if (StrStr(message, "name") ||
9223             StrStr(message, "rating") || StrStr(message, "?") ||
9224             StrStr(message, "result") || StrStr(message, "board") ||
9225             StrStr(message, "bk") || StrStr(message, "computer") ||
9226             StrStr(message, "variant") || StrStr(message, "hint") ||
9227             StrStr(message, "random") || StrStr(message, "depth") ||
9228             StrStr(message, "accepted")) {
9229             return;
9230         }
9231         if (StrStr(message, "protover")) {
9232           /* Program is responding to input, so it's apparently done
9233              initializing, and this error message indicates it is
9234              protocol version 1.  So we don't need to wait any longer
9235              for it to initialize and send feature commands. */
9236           FeatureDone(cps, 1);
9237           cps->protocolVersion = 1;
9238           return;
9239         }
9240         cps->maybeThinking = FALSE;
9241
9242         if (StrStr(message, "draw")) {
9243             /* Program doesn't have "draw" command */
9244             cps->sendDrawOffers = 0;
9245             return;
9246         }
9247         if (cps->sendTime != 1 &&
9248             (StrStr(message, "time") || StrStr(message, "otim"))) {
9249           /* Program apparently doesn't have "time" or "otim" command */
9250           cps->sendTime = 0;
9251           return;
9252         }
9253         if (StrStr(message, "analyze")) {
9254             cps->analysisSupport = FALSE;
9255             cps->analyzing = FALSE;
9256 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9257             EditGameEvent(); // [HGM] try to preserve loaded game
9258             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9259             DisplayError(buf2, 0);
9260             return;
9261         }
9262         if (StrStr(message, "(no matching move)st")) {
9263           /* Special kludge for GNU Chess 4 only */
9264           cps->stKludge = TRUE;
9265           SendTimeControl(cps, movesPerSession, timeControl,
9266                           timeIncrement, appData.searchDepth,
9267                           searchTime);
9268           return;
9269         }
9270         if (StrStr(message, "(no matching move)sd")) {
9271           /* Special kludge for GNU Chess 4 only */
9272           cps->sdKludge = TRUE;
9273           SendTimeControl(cps, movesPerSession, timeControl,
9274                           timeIncrement, appData.searchDepth,
9275                           searchTime);
9276           return;
9277         }
9278         if (!StrStr(message, "llegal")) {
9279             return;
9280         }
9281         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9282             gameMode == IcsIdle) return;
9283         if (forwardMostMove <= backwardMostMove) return;
9284         if (pausing) PauseEvent();
9285       if(appData.forceIllegal) {
9286             // [HGM] illegal: machine refused move; force position after move into it
9287           SendToProgram("force\n", cps);
9288           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9289                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9290                 // when black is to move, while there might be nothing on a2 or black
9291                 // might already have the move. So send the board as if white has the move.
9292                 // But first we must change the stm of the engine, as it refused the last move
9293                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9294                 if(WhiteOnMove(forwardMostMove)) {
9295                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9296                     SendBoard(cps, forwardMostMove); // kludgeless board
9297                 } else {
9298                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9299                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9300                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9301                 }
9302           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9303             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9304                  gameMode == TwoMachinesPlay)
9305               SendToProgram("go\n", cps);
9306             return;
9307       } else
9308         if (gameMode == PlayFromGameFile) {
9309             /* Stop reading this game file */
9310             gameMode = EditGame;
9311             ModeHighlight();
9312         }
9313         /* [HGM] illegal-move claim should forfeit game when Xboard */
9314         /* only passes fully legal moves                            */
9315         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9316             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9317                                 "False illegal-move claim", GE_XBOARD );
9318             return; // do not take back move we tested as valid
9319         }
9320         currentMove = forwardMostMove-1;
9321         DisplayMove(currentMove-1); /* before DisplayMoveError */
9322         SwitchClocks(forwardMostMove-1); // [HGM] race
9323         DisplayBothClocks();
9324         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9325                 parseList[currentMove], _(cps->which));
9326         DisplayMoveError(buf1);
9327         DrawPosition(FALSE, boards[currentMove]);
9328
9329         SetUserThinkingEnables();
9330         return;
9331     }
9332     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9333         /* Program has a broken "time" command that
9334            outputs a string not ending in newline.
9335            Don't use it. */
9336         cps->sendTime = 0;
9337     }
9338     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9339         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9340             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9341     }
9342
9343     /*
9344      * If chess program startup fails, exit with an error message.
9345      * Attempts to recover here are futile. [HGM] Well, we try anyway
9346      */
9347     if ((StrStr(message, "unknown host") != NULL)
9348         || (StrStr(message, "No remote directory") != NULL)
9349         || (StrStr(message, "not found") != NULL)
9350         || (StrStr(message, "No such file") != NULL)
9351         || (StrStr(message, "can't alloc") != NULL)
9352         || (StrStr(message, "Permission denied") != NULL)) {
9353
9354         cps->maybeThinking = FALSE;
9355         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9356                 _(cps->which), cps->program, cps->host, message);
9357         RemoveInputSource(cps->isr);
9358         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9359             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9360             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9361         }
9362         return;
9363     }
9364
9365     /*
9366      * Look for hint output
9367      */
9368     if (sscanf(message, "Hint: %s", buf1) == 1) {
9369         if (cps == &first && hintRequested) {
9370             hintRequested = FALSE;
9371             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9372                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9373                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9374                                     PosFlags(forwardMostMove),
9375                                     fromY, fromX, toY, toX, promoChar, buf1);
9376                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9377                 DisplayInformation(buf2);
9378             } else {
9379                 /* Hint move could not be parsed!? */
9380               snprintf(buf2, sizeof(buf2),
9381                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9382                         buf1, _(cps->which));
9383                 DisplayError(buf2, 0);
9384             }
9385         } else {
9386           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9387         }
9388         return;
9389     }
9390
9391     /*
9392      * Ignore other messages if game is not in progress
9393      */
9394     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9395         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9396
9397     /*
9398      * look for win, lose, draw, or draw offer
9399      */
9400     if (strncmp(message, "1-0", 3) == 0) {
9401         char *p, *q, *r = "";
9402         p = strchr(message, '{');
9403         if (p) {
9404             q = strchr(p, '}');
9405             if (q) {
9406                 *q = NULLCHAR;
9407                 r = p + 1;
9408             }
9409         }
9410         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9411         return;
9412     } else if (strncmp(message, "0-1", 3) == 0) {
9413         char *p, *q, *r = "";
9414         p = strchr(message, '{');
9415         if (p) {
9416             q = strchr(p, '}');
9417             if (q) {
9418                 *q = NULLCHAR;
9419                 r = p + 1;
9420             }
9421         }
9422         /* Kludge for Arasan 4.1 bug */
9423         if (strcmp(r, "Black resigns") == 0) {
9424             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9425             return;
9426         }
9427         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9428         return;
9429     } else if (strncmp(message, "1/2", 3) == 0) {
9430         char *p, *q, *r = "";
9431         p = strchr(message, '{');
9432         if (p) {
9433             q = strchr(p, '}');
9434             if (q) {
9435                 *q = NULLCHAR;
9436                 r = p + 1;
9437             }
9438         }
9439
9440         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9441         return;
9442
9443     } else if (strncmp(message, "White resign", 12) == 0) {
9444         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9445         return;
9446     } else if (strncmp(message, "Black resign", 12) == 0) {
9447         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9448         return;
9449     } else if (strncmp(message, "White matches", 13) == 0 ||
9450                strncmp(message, "Black matches", 13) == 0   ) {
9451         /* [HGM] ignore GNUShogi noises */
9452         return;
9453     } else if (strncmp(message, "White", 5) == 0 &&
9454                message[5] != '(' &&
9455                StrStr(message, "Black") == NULL) {
9456         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9457         return;
9458     } else if (strncmp(message, "Black", 5) == 0 &&
9459                message[5] != '(') {
9460         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9461         return;
9462     } else if (strcmp(message, "resign") == 0 ||
9463                strcmp(message, "computer resigns") == 0) {
9464         switch (gameMode) {
9465           case MachinePlaysBlack:
9466           case IcsPlayingBlack:
9467             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9468             break;
9469           case MachinePlaysWhite:
9470           case IcsPlayingWhite:
9471             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9472             break;
9473           case TwoMachinesPlay:
9474             if (cps->twoMachinesColor[0] == 'w')
9475               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9476             else
9477               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9478             break;
9479           default:
9480             /* can't happen */
9481             break;
9482         }
9483         return;
9484     } else if (strncmp(message, "opponent mates", 14) == 0) {
9485         switch (gameMode) {
9486           case MachinePlaysBlack:
9487           case IcsPlayingBlack:
9488             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9489             break;
9490           case MachinePlaysWhite:
9491           case IcsPlayingWhite:
9492             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9493             break;
9494           case TwoMachinesPlay:
9495             if (cps->twoMachinesColor[0] == 'w')
9496               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9497             else
9498               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9499             break;
9500           default:
9501             /* can't happen */
9502             break;
9503         }
9504         return;
9505     } else if (strncmp(message, "computer mates", 14) == 0) {
9506         switch (gameMode) {
9507           case MachinePlaysBlack:
9508           case IcsPlayingBlack:
9509             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9510             break;
9511           case MachinePlaysWhite:
9512           case IcsPlayingWhite:
9513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9514             break;
9515           case TwoMachinesPlay:
9516             if (cps->twoMachinesColor[0] == 'w')
9517               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9518             else
9519               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9520             break;
9521           default:
9522             /* can't happen */
9523             break;
9524         }
9525         return;
9526     } else if (strncmp(message, "checkmate", 9) == 0) {
9527         if (WhiteOnMove(forwardMostMove)) {
9528             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9529         } else {
9530             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9531         }
9532         return;
9533     } else if (strstr(message, "Draw") != NULL ||
9534                strstr(message, "game is a draw") != NULL) {
9535         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9536         return;
9537     } else if (strstr(message, "offer") != NULL &&
9538                strstr(message, "draw") != NULL) {
9539 #if ZIPPY
9540         if (appData.zippyPlay && first.initDone) {
9541             /* Relay offer to ICS */
9542             SendToICS(ics_prefix);
9543             SendToICS("draw\n");
9544         }
9545 #endif
9546         cps->offeredDraw = 2; /* valid until this engine moves twice */
9547         if (gameMode == TwoMachinesPlay) {
9548             if (cps->other->offeredDraw) {
9549                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9550             /* [HGM] in two-machine mode we delay relaying draw offer      */
9551             /* until after we also have move, to see if it is really claim */
9552             }
9553         } else if (gameMode == MachinePlaysWhite ||
9554                    gameMode == MachinePlaysBlack) {
9555           if (userOfferedDraw) {
9556             DisplayInformation(_("Machine accepts your draw offer"));
9557             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9558           } else {
9559             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9560           }
9561         }
9562     }
9563
9564
9565     /*
9566      * Look for thinking output
9567      */
9568     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9569           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9570                                 ) {
9571         int plylev, mvleft, mvtot, curscore, time;
9572         char mvname[MOVE_LEN];
9573         u64 nodes; // [DM]
9574         char plyext;
9575         int ignore = FALSE;
9576         int prefixHint = FALSE;
9577         mvname[0] = NULLCHAR;
9578
9579         switch (gameMode) {
9580           case MachinePlaysBlack:
9581           case IcsPlayingBlack:
9582             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9583             break;
9584           case MachinePlaysWhite:
9585           case IcsPlayingWhite:
9586             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9587             break;
9588           case AnalyzeMode:
9589           case AnalyzeFile:
9590             break;
9591           case IcsObserving: /* [DM] icsEngineAnalyze */
9592             if (!appData.icsEngineAnalyze) ignore = TRUE;
9593             break;
9594           case TwoMachinesPlay:
9595             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9596                 ignore = TRUE;
9597             }
9598             break;
9599           default:
9600             ignore = TRUE;
9601             break;
9602         }
9603
9604         if (!ignore) {
9605             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9606             buf1[0] = NULLCHAR;
9607             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9608                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9609                 char score_buf[MSG_SIZ];
9610
9611                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9612                     nodes += u64Const(0x100000000);
9613
9614                 if (plyext != ' ' && plyext != '\t') {
9615                     time *= 100;
9616                 }
9617
9618                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9619                 if( cps->scoreIsAbsolute &&
9620                     ( gameMode == MachinePlaysBlack ||
9621                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9622                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9623                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9624                      !WhiteOnMove(currentMove)
9625                     ) )
9626                 {
9627                     curscore = -curscore;
9628                 }
9629
9630                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9631
9632                 if(*bestMove) { // rememer time best EPD move was first found
9633                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9634                     ChessMove mt;
9635                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9636                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9637                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9638                 }
9639
9640                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9641                         char buf[MSG_SIZ];
9642                         FILE *f;
9643                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9644                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9645                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9646                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9647                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9648                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9649                                 fclose(f);
9650                         }
9651                         else
9652                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9653                           DisplayError(_("failed writing PV"), 0);
9654                 }
9655
9656                 tempStats.depth = plylev;
9657                 tempStats.nodes = nodes;
9658                 tempStats.time = time;
9659                 tempStats.score = curscore;
9660                 tempStats.got_only_move = 0;
9661
9662                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9663                         int ticklen;
9664
9665                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9666                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9667                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9668                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9669                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9670                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9671                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9672                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9673                 }
9674
9675                 /* Buffer overflow protection */
9676                 if (pv[0] != NULLCHAR) {
9677                     if (strlen(pv) >= sizeof(tempStats.movelist)
9678                         && appData.debugMode) {
9679                         fprintf(debugFP,
9680                                 "PV is too long; using the first %u bytes.\n",
9681                                 (unsigned) sizeof(tempStats.movelist) - 1);
9682                     }
9683
9684                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9685                 } else {
9686                     sprintf(tempStats.movelist, " no PV\n");
9687                 }
9688
9689                 if (tempStats.seen_stat) {
9690                     tempStats.ok_to_send = 1;
9691                 }
9692
9693                 if (strchr(tempStats.movelist, '(') != NULL) {
9694                     tempStats.line_is_book = 1;
9695                     tempStats.nr_moves = 0;
9696                     tempStats.moves_left = 0;
9697                 } else {
9698                     tempStats.line_is_book = 0;
9699                 }
9700
9701                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9702                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9703
9704                 SendProgramStatsToFrontend( cps, &tempStats );
9705
9706                 /*
9707                     [AS] Protect the thinkOutput buffer from overflow... this
9708                     is only useful if buf1 hasn't overflowed first!
9709                 */
9710                 if(curscore >= MATE_SCORE) 
9711                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9712                 else if(curscore <= -MATE_SCORE) 
9713                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9714                 else
9715                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9716                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9717                          plylev,
9718                          (gameMode == TwoMachinesPlay ?
9719                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9720                          score_buf,
9721                          prefixHint ? lastHint : "",
9722                          prefixHint ? " " : "" );
9723
9724                 if( buf1[0] != NULLCHAR ) {
9725                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9726
9727                     if( strlen(pv) > max_len ) {
9728                         if( appData.debugMode) {
9729                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9730                         }
9731                         pv[max_len+1] = '\0';
9732                     }
9733
9734                     strcat( thinkOutput, pv);
9735                 }
9736
9737                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9738                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9739                     DisplayMove(currentMove - 1);
9740                 }
9741                 return;
9742
9743             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9744                 /* crafty (9.25+) says "(only move) <move>"
9745                  * if there is only 1 legal move
9746                  */
9747                 sscanf(p, "(only move) %s", buf1);
9748                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9749                 sprintf(programStats.movelist, "%s (only move)", buf1);
9750                 programStats.depth = 1;
9751                 programStats.nr_moves = 1;
9752                 programStats.moves_left = 1;
9753                 programStats.nodes = 1;
9754                 programStats.time = 1;
9755                 programStats.got_only_move = 1;
9756
9757                 /* Not really, but we also use this member to
9758                    mean "line isn't going to change" (Crafty
9759                    isn't searching, so stats won't change) */
9760                 programStats.line_is_book = 1;
9761
9762                 SendProgramStatsToFrontend( cps, &programStats );
9763
9764                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9765                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9766                     DisplayMove(currentMove - 1);
9767                 }
9768                 return;
9769             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9770                               &time, &nodes, &plylev, &mvleft,
9771                               &mvtot, mvname) >= 5) {
9772                 /* The stat01: line is from Crafty (9.29+) in response
9773                    to the "." command */
9774                 programStats.seen_stat = 1;
9775                 cps->maybeThinking = TRUE;
9776
9777                 if (programStats.got_only_move || !appData.periodicUpdates)
9778                   return;
9779
9780                 programStats.depth = plylev;
9781                 programStats.time = time;
9782                 programStats.nodes = nodes;
9783                 programStats.moves_left = mvleft;
9784                 programStats.nr_moves = mvtot;
9785                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9786                 programStats.ok_to_send = 1;
9787                 programStats.movelist[0] = '\0';
9788
9789                 SendProgramStatsToFrontend( cps, &programStats );
9790
9791                 return;
9792
9793             } else if (strncmp(message,"++",2) == 0) {
9794                 /* Crafty 9.29+ outputs this */
9795                 programStats.got_fail = 2;
9796                 return;
9797
9798             } else if (strncmp(message,"--",2) == 0) {
9799                 /* Crafty 9.29+ outputs this */
9800                 programStats.got_fail = 1;
9801                 return;
9802
9803             } else if (thinkOutput[0] != NULLCHAR &&
9804                        strncmp(message, "    ", 4) == 0) {
9805                 unsigned message_len;
9806
9807                 p = message;
9808                 while (*p && *p == ' ') p++;
9809
9810                 message_len = strlen( p );
9811
9812                 /* [AS] Avoid buffer overflow */
9813                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9814                     strcat(thinkOutput, " ");
9815                     strcat(thinkOutput, p);
9816                 }
9817
9818                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9819                     strcat(programStats.movelist, " ");
9820                     strcat(programStats.movelist, p);
9821                 }
9822
9823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9825                     DisplayMove(currentMove - 1);
9826                 }
9827                 return;
9828             }
9829         }
9830         else {
9831             buf1[0] = NULLCHAR;
9832
9833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9835             {
9836                 ChessProgramStats cpstats;
9837
9838                 if (plyext != ' ' && plyext != '\t') {
9839                     time *= 100;
9840                 }
9841
9842                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9843                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9844                     curscore = -curscore;
9845                 }
9846
9847                 cpstats.depth = plylev;
9848                 cpstats.nodes = nodes;
9849                 cpstats.time = time;
9850                 cpstats.score = curscore;
9851                 cpstats.got_only_move = 0;
9852                 cpstats.movelist[0] = '\0';
9853
9854                 if (buf1[0] != NULLCHAR) {
9855                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9856                 }
9857
9858                 cpstats.ok_to_send = 0;
9859                 cpstats.line_is_book = 0;
9860                 cpstats.nr_moves = 0;
9861                 cpstats.moves_left = 0;
9862
9863                 SendProgramStatsToFrontend( cps, &cpstats );
9864             }
9865         }
9866     }
9867 }
9868
9869
9870 /* Parse a game score from the character string "game", and
9871    record it as the history of the current game.  The game
9872    score is NOT assumed to start from the standard position.
9873    The display is not updated in any way.
9874    */
9875 void
9876 ParseGameHistory (char *game)
9877 {
9878     ChessMove moveType;
9879     int fromX, fromY, toX, toY, boardIndex;
9880     char promoChar;
9881     char *p, *q;
9882     char buf[MSG_SIZ];
9883
9884     if (appData.debugMode)
9885       fprintf(debugFP, "Parsing game history: %s\n", game);
9886
9887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9888     gameInfo.site = StrSave(appData.icsHost);
9889     gameInfo.date = PGNDate();
9890     gameInfo.round = StrSave("-");
9891
9892     /* Parse out names of players */
9893     while (*game == ' ') game++;
9894     p = buf;
9895     while (*game != ' ') *p++ = *game++;
9896     *p = NULLCHAR;
9897     gameInfo.white = StrSave(buf);
9898     while (*game == ' ') game++;
9899     p = buf;
9900     while (*game != ' ' && *game != '\n') *p++ = *game++;
9901     *p = NULLCHAR;
9902     gameInfo.black = StrSave(buf);
9903
9904     /* Parse moves */
9905     boardIndex = blackPlaysFirst ? 1 : 0;
9906     yynewstr(game);
9907     for (;;) {
9908         yyboardindex = boardIndex;
9909         moveType = (ChessMove) Myylex();
9910         switch (moveType) {
9911           case IllegalMove:             /* maybe suicide chess, etc. */
9912   if (appData.debugMode) {
9913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9915     setbuf(debugFP, NULL);
9916   }
9917           case WhitePromotion:
9918           case BlackPromotion:
9919           case WhiteNonPromotion:
9920           case BlackNonPromotion:
9921           case NormalMove:
9922           case FirstLeg:
9923           case WhiteCapturesEnPassant:
9924           case BlackCapturesEnPassant:
9925           case WhiteKingSideCastle:
9926           case WhiteQueenSideCastle:
9927           case BlackKingSideCastle:
9928           case BlackQueenSideCastle:
9929           case WhiteKingSideCastleWild:
9930           case WhiteQueenSideCastleWild:
9931           case BlackKingSideCastleWild:
9932           case BlackQueenSideCastleWild:
9933           /* PUSH Fabien */
9934           case WhiteHSideCastleFR:
9935           case WhiteASideCastleFR:
9936           case BlackHSideCastleFR:
9937           case BlackASideCastleFR:
9938           /* POP Fabien */
9939             fromX = currentMoveString[0] - AAA;
9940             fromY = currentMoveString[1] - ONE;
9941             toX = currentMoveString[2] - AAA;
9942             toY = currentMoveString[3] - ONE;
9943             promoChar = currentMoveString[4];
9944             break;
9945           case WhiteDrop:
9946           case BlackDrop:
9947             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9948             fromX = moveType == WhiteDrop ?
9949               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9950             (int) CharToPiece(ToLower(currentMoveString[0]));
9951             fromY = DROP_RANK;
9952             toX = currentMoveString[2] - AAA;
9953             toY = currentMoveString[3] - ONE;
9954             promoChar = NULLCHAR;
9955             break;
9956           case AmbiguousMove:
9957             /* bug? */
9958             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9959   if (appData.debugMode) {
9960     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9961     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9962     setbuf(debugFP, NULL);
9963   }
9964             DisplayError(buf, 0);
9965             return;
9966           case ImpossibleMove:
9967             /* bug? */
9968             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9969   if (appData.debugMode) {
9970     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9972     setbuf(debugFP, NULL);
9973   }
9974             DisplayError(buf, 0);
9975             return;
9976           case EndOfFile:
9977             if (boardIndex < backwardMostMove) {
9978                 /* Oops, gap.  How did that happen? */
9979                 DisplayError(_("Gap in move list"), 0);
9980                 return;
9981             }
9982             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9983             if (boardIndex > forwardMostMove) {
9984                 forwardMostMove = boardIndex;
9985             }
9986             return;
9987           case ElapsedTime:
9988             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9989                 strcat(parseList[boardIndex-1], " ");
9990                 strcat(parseList[boardIndex-1], yy_text);
9991             }
9992             continue;
9993           case Comment:
9994           case PGNTag:
9995           case NAG:
9996           default:
9997             /* ignore */
9998             continue;
9999           case WhiteWins:
10000           case BlackWins:
10001           case GameIsDrawn:
10002           case GameUnfinished:
10003             if (gameMode == IcsExamining) {
10004                 if (boardIndex < backwardMostMove) {
10005                     /* Oops, gap.  How did that happen? */
10006                     return;
10007                 }
10008                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10009                 return;
10010             }
10011             gameInfo.result = moveType;
10012             p = strchr(yy_text, '{');
10013             if (p == NULL) p = strchr(yy_text, '(');
10014             if (p == NULL) {
10015                 p = yy_text;
10016                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10017             } else {
10018                 q = strchr(p, *p == '{' ? '}' : ')');
10019                 if (q != NULL) *q = NULLCHAR;
10020                 p++;
10021             }
10022             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10023             gameInfo.resultDetails = StrSave(p);
10024             continue;
10025         }
10026         if (boardIndex >= forwardMostMove &&
10027             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10028             backwardMostMove = blackPlaysFirst ? 1 : 0;
10029             return;
10030         }
10031         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10032                                  fromY, fromX, toY, toX, promoChar,
10033                                  parseList[boardIndex]);
10034         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10035         /* currentMoveString is set as a side-effect of yylex */
10036         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10037         strcat(moveList[boardIndex], "\n");
10038         boardIndex++;
10039         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10040         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10041           case MT_NONE:
10042           case MT_STALEMATE:
10043           default:
10044             break;
10045           case MT_CHECK:
10046             if(!IS_SHOGI(gameInfo.variant))
10047                 strcat(parseList[boardIndex - 1], "+");
10048             break;
10049           case MT_CHECKMATE:
10050           case MT_STAINMATE:
10051             strcat(parseList[boardIndex - 1], "#");
10052             break;
10053         }
10054     }
10055 }
10056
10057
10058 /* Apply a move to the given board  */
10059 void
10060 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10061 {
10062   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10063   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10064
10065     /* [HGM] compute & store e.p. status and castling rights for new position */
10066     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10067
10068       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10069       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10070       board[EP_STATUS] = EP_NONE;
10071       board[EP_FILE] = board[EP_RANK] = 100;
10072
10073   if (fromY == DROP_RANK) {
10074         /* must be first */
10075         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10076             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10077             return;
10078         }
10079         piece = board[toY][toX] = (ChessSquare) fromX;
10080   } else {
10081 //      ChessSquare victim;
10082       int i;
10083
10084       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10085 //           victim = board[killY][killX],
10086            killed = board[killY][killX],
10087            board[killY][killX] = EmptySquare,
10088            board[EP_STATUS] = EP_CAPTURE;
10089            if( kill2X >= 0 && kill2Y >= 0)
10090              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10091       }
10092
10093       if( board[toY][toX] != EmptySquare ) {
10094            board[EP_STATUS] = EP_CAPTURE;
10095            if( (fromX != toX || fromY != toY) && // not igui!
10096                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10097                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10098                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10099            }
10100       }
10101
10102       pawn = board[fromY][fromX];
10103       if( pawn == WhiteLance || pawn == BlackLance ) {
10104            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10105                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10106                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10107            }
10108       }
10109       if( pawn == WhitePawn ) {
10110            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10111                board[EP_STATUS] = EP_PAWN_MOVE;
10112            if( toY-fromY>=2) {
10113                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10114                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10115                         gameInfo.variant != VariantBerolina || toX < fromX)
10116                       board[EP_STATUS] = toX | berolina;
10117                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10118                         gameInfo.variant != VariantBerolina || toX > fromX)
10119                       board[EP_STATUS] = toX;
10120            }
10121       } else
10122       if( pawn == BlackPawn ) {
10123            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10124                board[EP_STATUS] = EP_PAWN_MOVE;
10125            if( toY-fromY<= -2) {
10126                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10127                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10128                         gameInfo.variant != VariantBerolina || toX < fromX)
10129                       board[EP_STATUS] = toX | berolina;
10130                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10131                         gameInfo.variant != VariantBerolina || toX > fromX)
10132                       board[EP_STATUS] = toX;
10133            }
10134        }
10135
10136        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10137        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10138        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10139        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10140
10141        for(i=0; i<nrCastlingRights; i++) {
10142            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10143               board[CASTLING][i] == toX   && castlingRank[i] == toY
10144              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10145        }
10146
10147        if(gameInfo.variant == VariantSChess) { // update virginity
10148            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10149            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10150            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10151            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10152        }
10153
10154      if (fromX == toX && fromY == toY) return;
10155
10156      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10157      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10158      if(gameInfo.variant == VariantKnightmate)
10159          king += (int) WhiteUnicorn - (int) WhiteKing;
10160
10161     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10162        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10163         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10164         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10165         board[EP_STATUS] = EP_NONE; // capture was fake!
10166     } else
10167     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10168         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10169         board[toY][toX] = piece;
10170         board[EP_STATUS] = EP_NONE; // capture was fake!
10171     } else
10172     /* Code added by Tord: */
10173     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10174     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10175         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10176       board[EP_STATUS] = EP_NONE; // capture was fake!
10177       board[fromY][fromX] = EmptySquare;
10178       board[toY][toX] = EmptySquare;
10179       if((toX > fromX) != (piece == WhiteRook)) {
10180         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10181       } else {
10182         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10183       }
10184     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10185                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10186       board[EP_STATUS] = EP_NONE;
10187       board[fromY][fromX] = EmptySquare;
10188       board[toY][toX] = EmptySquare;
10189       if((toX > fromX) != (piece == BlackRook)) {
10190         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10191       } else {
10192         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10193       }
10194     /* End of code added by Tord */
10195
10196     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10197         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10198         board[toY][toX] = piece;
10199     } else if (board[fromY][fromX] == king
10200         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10201         && toY == fromY && toX > fromX+1) {
10202         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10203         board[fromY][toX-1] = board[fromY][rookX];
10204         board[fromY][rookX] = EmptySquare;
10205         board[fromY][fromX] = EmptySquare;
10206         board[toY][toX] = king;
10207     } else if (board[fromY][fromX] == king
10208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10209                && toY == fromY && toX < fromX-1) {
10210         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10211         board[fromY][toX+1] = board[fromY][rookX];
10212         board[fromY][rookX] = EmptySquare;
10213         board[fromY][fromX] = EmptySquare;
10214         board[toY][toX] = king;
10215     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10216                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10217                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10218                ) {
10219         /* white pawn promotion */
10220         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10221         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10222             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10223         board[fromY][fromX] = EmptySquare;
10224     } else if ((fromY >= BOARD_HEIGHT>>1)
10225                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10226                && (toX != fromX)
10227                && gameInfo.variant != VariantXiangqi
10228                && gameInfo.variant != VariantBerolina
10229                && (pawn == WhitePawn)
10230                && (board[toY][toX] == EmptySquare)) {
10231         board[fromY][fromX] = EmptySquare;
10232         board[toY][toX] = piece;
10233         if(toY == epRank - 128 + 1)
10234             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10235         else
10236             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10237     } else if ((fromY == BOARD_HEIGHT-4)
10238                && (toX == fromX)
10239                && gameInfo.variant == VariantBerolina
10240                && (board[fromY][fromX] == WhitePawn)
10241                && (board[toY][toX] == EmptySquare)) {
10242         board[fromY][fromX] = EmptySquare;
10243         board[toY][toX] = WhitePawn;
10244         if(oldEP & EP_BEROLIN_A) {
10245                 captured = board[fromY][fromX-1];
10246                 board[fromY][fromX-1] = EmptySquare;
10247         }else{  captured = board[fromY][fromX+1];
10248                 board[fromY][fromX+1] = EmptySquare;
10249         }
10250     } else if (board[fromY][fromX] == king
10251         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10252                && toY == fromY && toX > fromX+1) {
10253         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10254         board[fromY][toX-1] = board[fromY][rookX];
10255         board[fromY][rookX] = EmptySquare;
10256         board[fromY][fromX] = EmptySquare;
10257         board[toY][toX] = king;
10258     } else if (board[fromY][fromX] == king
10259         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10260                && toY == fromY && toX < fromX-1) {
10261         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10262         board[fromY][toX+1] = board[fromY][rookX];
10263         board[fromY][rookX] = EmptySquare;
10264         board[fromY][fromX] = EmptySquare;
10265         board[toY][toX] = king;
10266     } else if (fromY == 7 && fromX == 3
10267                && board[fromY][fromX] == BlackKing
10268                && toY == 7 && toX == 5) {
10269         board[fromY][fromX] = EmptySquare;
10270         board[toY][toX] = BlackKing;
10271         board[fromY][7] = EmptySquare;
10272         board[toY][4] = BlackRook;
10273     } else if (fromY == 7 && fromX == 3
10274                && board[fromY][fromX] == BlackKing
10275                && toY == 7 && toX == 1) {
10276         board[fromY][fromX] = EmptySquare;
10277         board[toY][toX] = BlackKing;
10278         board[fromY][0] = EmptySquare;
10279         board[toY][2] = BlackRook;
10280     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10281                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10282                && toY < promoRank && promoChar
10283                ) {
10284         /* black pawn promotion */
10285         board[toY][toX] = CharToPiece(ToLower(promoChar));
10286         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10287             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10288         board[fromY][fromX] = EmptySquare;
10289     } else if ((fromY < BOARD_HEIGHT>>1)
10290                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10291                && (toX != fromX)
10292                && gameInfo.variant != VariantXiangqi
10293                && gameInfo.variant != VariantBerolina
10294                && (pawn == BlackPawn)
10295                && (board[toY][toX] == EmptySquare)) {
10296         board[fromY][fromX] = EmptySquare;
10297         board[toY][toX] = piece;
10298         if(toY == epRank - 128 - 1)
10299             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10300         else
10301             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10302     } else if ((fromY == 3)
10303                && (toX == fromX)
10304                && gameInfo.variant == VariantBerolina
10305                && (board[fromY][fromX] == BlackPawn)
10306                && (board[toY][toX] == EmptySquare)) {
10307         board[fromY][fromX] = EmptySquare;
10308         board[toY][toX] = BlackPawn;
10309         if(oldEP & EP_BEROLIN_A) {
10310                 captured = board[fromY][fromX-1];
10311                 board[fromY][fromX-1] = EmptySquare;
10312         }else{  captured = board[fromY][fromX+1];
10313                 board[fromY][fromX+1] = EmptySquare;
10314         }
10315     } else {
10316         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10317         board[fromY][fromX] = EmptySquare;
10318         board[toY][toX] = piece;
10319     }
10320   }
10321
10322     if (gameInfo.holdingsWidth != 0) {
10323
10324       /* !!A lot more code needs to be written to support holdings  */
10325       /* [HGM] OK, so I have written it. Holdings are stored in the */
10326       /* penultimate board files, so they are automaticlly stored   */
10327       /* in the game history.                                       */
10328       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10329                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10330         /* Delete from holdings, by decreasing count */
10331         /* and erasing image if necessary            */
10332         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10333         if(p < (int) BlackPawn) { /* white drop */
10334              p -= (int)WhitePawn;
10335                  p = PieceToNumber((ChessSquare)p);
10336              if(p >= gameInfo.holdingsSize) p = 0;
10337              if(--board[p][BOARD_WIDTH-2] <= 0)
10338                   board[p][BOARD_WIDTH-1] = EmptySquare;
10339              if((int)board[p][BOARD_WIDTH-2] < 0)
10340                         board[p][BOARD_WIDTH-2] = 0;
10341         } else {                  /* black drop */
10342              p -= (int)BlackPawn;
10343                  p = PieceToNumber((ChessSquare)p);
10344              if(p >= gameInfo.holdingsSize) p = 0;
10345              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10346                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10347              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10348                         board[BOARD_HEIGHT-1-p][1] = 0;
10349         }
10350       }
10351       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10352           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10353         /* [HGM] holdings: Add to holdings, if holdings exist */
10354         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10355                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10356                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10357         }
10358         p = (int) captured;
10359         if (p >= (int) BlackPawn) {
10360           p -= (int)BlackPawn;
10361           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10362                   /* Restore shogi-promoted piece to its original  first */
10363                   captured = (ChessSquare) (DEMOTED(captured));
10364                   p = DEMOTED(p);
10365           }
10366           p = PieceToNumber((ChessSquare)p);
10367           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10368           board[p][BOARD_WIDTH-2]++;
10369           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10370         } else {
10371           p -= (int)WhitePawn;
10372           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10373                   captured = (ChessSquare) (DEMOTED(captured));
10374                   p = DEMOTED(p);
10375           }
10376           p = PieceToNumber((ChessSquare)p);
10377           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10378           board[BOARD_HEIGHT-1-p][1]++;
10379           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10380         }
10381       }
10382     } else if (gameInfo.variant == VariantAtomic) {
10383       if (captured != EmptySquare) {
10384         int y, x;
10385         for (y = toY-1; y <= toY+1; y++) {
10386           for (x = toX-1; x <= toX+1; x++) {
10387             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10388                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10389               board[y][x] = EmptySquare;
10390             }
10391           }
10392         }
10393         board[toY][toX] = EmptySquare;
10394       }
10395     }
10396
10397     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10398         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10399     } else
10400     if(promoChar == '+') {
10401         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10402         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10403         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10404           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10405     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10406         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10407         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10408            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10409         board[toY][toX] = newPiece;
10410     }
10411     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10412                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10413         // [HGM] superchess: take promotion piece out of holdings
10414         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10415         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10416             if(!--board[k][BOARD_WIDTH-2])
10417                 board[k][BOARD_WIDTH-1] = EmptySquare;
10418         } else {
10419             if(!--board[BOARD_HEIGHT-1-k][1])
10420                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10421         }
10422     }
10423 }
10424
10425 /* Updates forwardMostMove */
10426 void
10427 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10428 {
10429     int x = toX, y = toY;
10430     char *s = parseList[forwardMostMove];
10431     ChessSquare p = boards[forwardMostMove][toY][toX];
10432 //    forwardMostMove++; // [HGM] bare: moved downstream
10433
10434     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10435     (void) CoordsToAlgebraic(boards[forwardMostMove],
10436                              PosFlags(forwardMostMove),
10437                              fromY, fromX, y, x, promoChar,
10438                              s);
10439     if(killX >= 0 && killY >= 0)
10440         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10441
10442     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10443         int timeLeft; static int lastLoadFlag=0; int king, piece;
10444         piece = boards[forwardMostMove][fromY][fromX];
10445         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10446         if(gameInfo.variant == VariantKnightmate)
10447             king += (int) WhiteUnicorn - (int) WhiteKing;
10448         if(forwardMostMove == 0) {
10449             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10450                 fprintf(serverMoves, "%s;", UserName());
10451             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10452                 fprintf(serverMoves, "%s;", second.tidy);
10453             fprintf(serverMoves, "%s;", first.tidy);
10454             if(gameMode == MachinePlaysWhite)
10455                 fprintf(serverMoves, "%s;", UserName());
10456             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10457                 fprintf(serverMoves, "%s;", second.tidy);
10458         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10459         lastLoadFlag = loadFlag;
10460         // print base move
10461         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10462         // print castling suffix
10463         if( toY == fromY && piece == king ) {
10464             if(toX-fromX > 1)
10465                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10466             if(fromX-toX >1)
10467                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10468         }
10469         // e.p. suffix
10470         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10471              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10472              boards[forwardMostMove][toY][toX] == EmptySquare
10473              && fromX != toX && fromY != toY)
10474                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10475         // promotion suffix
10476         if(promoChar != NULLCHAR) {
10477             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10478                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10479                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10480             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10481         }
10482         if(!loadFlag) {
10483                 char buf[MOVE_LEN*2], *p; int len;
10484             fprintf(serverMoves, "/%d/%d",
10485                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10486             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10487             else                      timeLeft = blackTimeRemaining/1000;
10488             fprintf(serverMoves, "/%d", timeLeft);
10489                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10490                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10491                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10492                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10493             fprintf(serverMoves, "/%s", buf);
10494         }
10495         fflush(serverMoves);
10496     }
10497
10498     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10499         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10500       return;
10501     }
10502     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10503     if (commentList[forwardMostMove+1] != NULL) {
10504         free(commentList[forwardMostMove+1]);
10505         commentList[forwardMostMove+1] = NULL;
10506     }
10507     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10508     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10509     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10510     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10511     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10512     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10513     adjustedClock = FALSE;
10514     gameInfo.result = GameUnfinished;
10515     if (gameInfo.resultDetails != NULL) {
10516         free(gameInfo.resultDetails);
10517         gameInfo.resultDetails = NULL;
10518     }
10519     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10520                               moveList[forwardMostMove - 1]);
10521     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10522       case MT_NONE:
10523       case MT_STALEMATE:
10524       default:
10525         break;
10526       case MT_CHECK:
10527         if(!IS_SHOGI(gameInfo.variant))
10528             strcat(parseList[forwardMostMove - 1], "+");
10529         break;
10530       case MT_CHECKMATE:
10531       case MT_STAINMATE:
10532         strcat(parseList[forwardMostMove - 1], "#");
10533         break;
10534     }
10535 }
10536
10537 /* Updates currentMove if not pausing */
10538 void
10539 ShowMove (int fromX, int fromY, int toX, int toY)
10540 {
10541     int instant = (gameMode == PlayFromGameFile) ?
10542         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10543     if(appData.noGUI) return;
10544     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10545         if (!instant) {
10546             if (forwardMostMove == currentMove + 1) {
10547                 AnimateMove(boards[forwardMostMove - 1],
10548                             fromX, fromY, toX, toY);
10549             }
10550         }
10551         currentMove = forwardMostMove;
10552     }
10553
10554     killX = killY = -1; // [HGM] lion: used up
10555
10556     if (instant) return;
10557
10558     DisplayMove(currentMove - 1);
10559     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10560             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10561                 SetHighlights(fromX, fromY, toX, toY);
10562             }
10563     }
10564     DrawPosition(FALSE, boards[currentMove]);
10565     DisplayBothClocks();
10566     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10567 }
10568
10569 void
10570 SendEgtPath (ChessProgramState *cps)
10571 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10572         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10573
10574         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10575
10576         while(*p) {
10577             char c, *q = name+1, *r, *s;
10578
10579             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10580             while(*p && *p != ',') *q++ = *p++;
10581             *q++ = ':'; *q = 0;
10582             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10583                 strcmp(name, ",nalimov:") == 0 ) {
10584                 // take nalimov path from the menu-changeable option first, if it is defined
10585               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10586                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10587             } else
10588             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10589                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10590                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10591                 s = r = StrStr(s, ":") + 1; // beginning of path info
10592                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10593                 c = *r; *r = 0;             // temporarily null-terminate path info
10594                     *--q = 0;               // strip of trailig ':' from name
10595                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10596                 *r = c;
10597                 SendToProgram(buf,cps);     // send egtbpath command for this format
10598             }
10599             if(*p == ',') p++; // read away comma to position for next format name
10600         }
10601 }
10602
10603 static int
10604 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10605 {
10606       int width = 8, height = 8, holdings = 0;             // most common sizes
10607       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10608       // correct the deviations default for each variant
10609       if( v == VariantXiangqi ) width = 9,  height = 10;
10610       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10611       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10612       if( v == VariantCapablanca || v == VariantCapaRandom ||
10613           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10614                                 width = 10;
10615       if( v == VariantCourier ) width = 12;
10616       if( v == VariantSuper )                            holdings = 8;
10617       if( v == VariantGreat )   width = 10,              holdings = 8;
10618       if( v == VariantSChess )                           holdings = 7;
10619       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10620       if( v == VariantChuChess) width = 10, height = 10;
10621       if( v == VariantChu )     width = 12, height = 12;
10622       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10623              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10624              holdingsSize >= 0 && holdingsSize != holdings;
10625 }
10626
10627 char variantError[MSG_SIZ];
10628
10629 char *
10630 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10631 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10632       char *p, *variant = VariantName(v);
10633       static char b[MSG_SIZ];
10634       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10635            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10636                                                holdingsSize, variant); // cook up sized variant name
10637            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10638            if(StrStr(list, b) == NULL) {
10639                // specific sized variant not known, check if general sizing allowed
10640                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10641                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10642                             boardWidth, boardHeight, holdingsSize, engine);
10643                    return NULL;
10644                }
10645                /* [HGM] here we really should compare with the maximum supported board size */
10646            }
10647       } else snprintf(b, MSG_SIZ,"%s", variant);
10648       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10649       p = StrStr(list, b);
10650       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10651       if(p == NULL) {
10652           // occurs not at all in list, or only as sub-string
10653           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10654           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10655               int l = strlen(variantError);
10656               char *q;
10657               while(p != list && p[-1] != ',') p--;
10658               q = strchr(p, ',');
10659               if(q) *q = NULLCHAR;
10660               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10661               if(q) *q= ',';
10662           }
10663           return NULL;
10664       }
10665       return b;
10666 }
10667
10668 void
10669 InitChessProgram (ChessProgramState *cps, int setup)
10670 /* setup needed to setup FRC opening position */
10671 {
10672     char buf[MSG_SIZ], *b;
10673     if (appData.noChessProgram) return;
10674     hintRequested = FALSE;
10675     bookRequested = FALSE;
10676
10677     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10678     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10679     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10680     if(cps->memSize) { /* [HGM] memory */
10681       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10682         SendToProgram(buf, cps);
10683     }
10684     SendEgtPath(cps); /* [HGM] EGT */
10685     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10686       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10687         SendToProgram(buf, cps);
10688     }
10689
10690     setboardSpoiledMachineBlack = FALSE;
10691     SendToProgram(cps->initString, cps);
10692     if (gameInfo.variant != VariantNormal &&
10693         gameInfo.variant != VariantLoadable
10694         /* [HGM] also send variant if board size non-standard */
10695         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10696
10697       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10698                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10699       if (b == NULL) {
10700         VariantClass v;
10701         char c, *q = cps->variants, *p = strchr(q, ',');
10702         if(p) *p = NULLCHAR;
10703         v = StringToVariant(q);
10704         DisplayError(variantError, 0);
10705         if(v != VariantUnknown && cps == &first) {
10706             int w, h, s;
10707             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10708                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10709             ASSIGN(appData.variant, q);
10710             Reset(TRUE, FALSE);
10711         }
10712         if(p) *p = ',';
10713         return;
10714       }
10715
10716       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10717       SendToProgram(buf, cps);
10718     }
10719     currentlyInitializedVariant = gameInfo.variant;
10720
10721     /* [HGM] send opening position in FRC to first engine */
10722     if(setup) {
10723           SendToProgram("force\n", cps);
10724           SendBoard(cps, 0);
10725           /* engine is now in force mode! Set flag to wake it up after first move. */
10726           setboardSpoiledMachineBlack = 1;
10727     }
10728
10729     if (cps->sendICS) {
10730       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10731       SendToProgram(buf, cps);
10732     }
10733     cps->maybeThinking = FALSE;
10734     cps->offeredDraw = 0;
10735     if (!appData.icsActive) {
10736         SendTimeControl(cps, movesPerSession, timeControl,
10737                         timeIncrement, appData.searchDepth,
10738                         searchTime);
10739     }
10740     if (appData.showThinking
10741         // [HGM] thinking: four options require thinking output to be sent
10742         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10743                                 ) {
10744         SendToProgram("post\n", cps);
10745     }
10746     SendToProgram("hard\n", cps);
10747     if (!appData.ponderNextMove) {
10748         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10749            it without being sure what state we are in first.  "hard"
10750            is not a toggle, so that one is OK.
10751          */
10752         SendToProgram("easy\n", cps);
10753     }
10754     if (cps->usePing) {
10755       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10756       SendToProgram(buf, cps);
10757     }
10758     cps->initDone = TRUE;
10759     ClearEngineOutputPane(cps == &second);
10760 }
10761
10762
10763 void
10764 ResendOptions (ChessProgramState *cps)
10765 { // send the stored value of the options
10766   int i;
10767   char buf[MSG_SIZ];
10768   Option *opt = cps->option;
10769   for(i=0; i<cps->nrOptions; i++, opt++) {
10770       switch(opt->type) {
10771         case Spin:
10772         case Slider:
10773         case CheckBox:
10774             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10775           break;
10776         case ComboBox:
10777           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10778           break;
10779         default:
10780             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10781           break;
10782         case Button:
10783         case SaveButton:
10784           continue;
10785       }
10786       SendToProgram(buf, cps);
10787   }
10788 }
10789
10790 void
10791 StartChessProgram (ChessProgramState *cps)
10792 {
10793     char buf[MSG_SIZ];
10794     int err;
10795
10796     if (appData.noChessProgram) return;
10797     cps->initDone = FALSE;
10798
10799     if (strcmp(cps->host, "localhost") == 0) {
10800         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10801     } else if (*appData.remoteShell == NULLCHAR) {
10802         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10803     } else {
10804         if (*appData.remoteUser == NULLCHAR) {
10805           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10806                     cps->program);
10807         } else {
10808           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10809                     cps->host, appData.remoteUser, cps->program);
10810         }
10811         err = StartChildProcess(buf, "", &cps->pr);
10812     }
10813
10814     if (err != 0) {
10815       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10816         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10817         if(cps != &first) return;
10818         appData.noChessProgram = TRUE;
10819         ThawUI();
10820         SetNCPMode();
10821 //      DisplayFatalError(buf, err, 1);
10822 //      cps->pr = NoProc;
10823 //      cps->isr = NULL;
10824         return;
10825     }
10826
10827     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10828     if (cps->protocolVersion > 1) {
10829       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10830       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10831         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10832         cps->comboCnt = 0;  //                and values of combo boxes
10833       }
10834       SendToProgram(buf, cps);
10835       if(cps->reload) ResendOptions(cps);
10836     } else {
10837       SendToProgram("xboard\n", cps);
10838     }
10839 }
10840
10841 void
10842 TwoMachinesEventIfReady P((void))
10843 {
10844   static int curMess = 0;
10845   if (first.lastPing != first.lastPong) {
10846     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10847     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10848     return;
10849   }
10850   if (second.lastPing != second.lastPong) {
10851     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10852     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10853     return;
10854   }
10855   DisplayMessage("", ""); curMess = 0;
10856   TwoMachinesEvent();
10857 }
10858
10859 char *
10860 MakeName (char *template)
10861 {
10862     time_t clock;
10863     struct tm *tm;
10864     static char buf[MSG_SIZ];
10865     char *p = buf;
10866     int i;
10867
10868     clock = time((time_t *)NULL);
10869     tm = localtime(&clock);
10870
10871     while(*p++ = *template++) if(p[-1] == '%') {
10872         switch(*template++) {
10873           case 0:   *p = 0; return buf;
10874           case 'Y': i = tm->tm_year+1900; break;
10875           case 'y': i = tm->tm_year-100; break;
10876           case 'M': i = tm->tm_mon+1; break;
10877           case 'd': i = tm->tm_mday; break;
10878           case 'h': i = tm->tm_hour; break;
10879           case 'm': i = tm->tm_min; break;
10880           case 's': i = tm->tm_sec; break;
10881           default:  i = 0;
10882         }
10883         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10884     }
10885     return buf;
10886 }
10887
10888 int
10889 CountPlayers (char *p)
10890 {
10891     int n = 0;
10892     while(p = strchr(p, '\n')) p++, n++; // count participants
10893     return n;
10894 }
10895
10896 FILE *
10897 WriteTourneyFile (char *results, FILE *f)
10898 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10899     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10900     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10901         // create a file with tournament description
10902         fprintf(f, "-participants {%s}\n", appData.participants);
10903         fprintf(f, "-seedBase %d\n", appData.seedBase);
10904         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10905         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10906         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10907         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10908         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10909         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10910         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10911         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10912         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10913         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10914         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10915         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10916         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10917         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10918         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10919         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10920         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10921         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10922         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10923         fprintf(f, "-smpCores %d\n", appData.smpCores);
10924         if(searchTime > 0)
10925                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10926         else {
10927                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10928                 fprintf(f, "-tc %s\n", appData.timeControl);
10929                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10930         }
10931         fprintf(f, "-results \"%s\"\n", results);
10932     }
10933     return f;
10934 }
10935
10936 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10937
10938 void
10939 Substitute (char *participants, int expunge)
10940 {
10941     int i, changed, changes=0, nPlayers=0;
10942     char *p, *q, *r, buf[MSG_SIZ];
10943     if(participants == NULL) return;
10944     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10945     r = p = participants; q = appData.participants;
10946     while(*p && *p == *q) {
10947         if(*p == '\n') r = p+1, nPlayers++;
10948         p++; q++;
10949     }
10950     if(*p) { // difference
10951         while(*p && *p++ != '\n');
10952         while(*q && *q++ != '\n');
10953       changed = nPlayers;
10954         changes = 1 + (strcmp(p, q) != 0);
10955     }
10956     if(changes == 1) { // a single engine mnemonic was changed
10957         q = r; while(*q) nPlayers += (*q++ == '\n');
10958         p = buf; while(*r && (*p = *r++) != '\n') p++;
10959         *p = NULLCHAR;
10960         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10961         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10962         if(mnemonic[i]) { // The substitute is valid
10963             FILE *f;
10964             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10965                 flock(fileno(f), LOCK_EX);
10966                 ParseArgsFromFile(f);
10967                 fseek(f, 0, SEEK_SET);
10968                 FREE(appData.participants); appData.participants = participants;
10969                 if(expunge) { // erase results of replaced engine
10970                     int len = strlen(appData.results), w, b, dummy;
10971                     for(i=0; i<len; i++) {
10972                         Pairing(i, nPlayers, &w, &b, &dummy);
10973                         if((w == changed || b == changed) && appData.results[i] == '*') {
10974                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10975                             fclose(f);
10976                             return;
10977                         }
10978                     }
10979                     for(i=0; i<len; i++) {
10980                         Pairing(i, nPlayers, &w, &b, &dummy);
10981                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10982                     }
10983                 }
10984                 WriteTourneyFile(appData.results, f);
10985                 fclose(f); // release lock
10986                 return;
10987             }
10988         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10989     }
10990     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10991     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10992     free(participants);
10993     return;
10994 }
10995
10996 int
10997 CheckPlayers (char *participants)
10998 {
10999         int i;
11000         char buf[MSG_SIZ], *p;
11001         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11002         while(p = strchr(participants, '\n')) {
11003             *p = NULLCHAR;
11004             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11005             if(!mnemonic[i]) {
11006                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11007                 *p = '\n';
11008                 DisplayError(buf, 0);
11009                 return 1;
11010             }
11011             *p = '\n';
11012             participants = p + 1;
11013         }
11014         return 0;
11015 }
11016
11017 int
11018 CreateTourney (char *name)
11019 {
11020         FILE *f;
11021         if(matchMode && strcmp(name, appData.tourneyFile)) {
11022              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11023         }
11024         if(name[0] == NULLCHAR) {
11025             if(appData.participants[0])
11026                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11027             return 0;
11028         }
11029         f = fopen(name, "r");
11030         if(f) { // file exists
11031             ASSIGN(appData.tourneyFile, name);
11032             ParseArgsFromFile(f); // parse it
11033         } else {
11034             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11035             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11036                 DisplayError(_("Not enough participants"), 0);
11037                 return 0;
11038             }
11039             if(CheckPlayers(appData.participants)) return 0;
11040             ASSIGN(appData.tourneyFile, name);
11041             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11042             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11043         }
11044         fclose(f);
11045         appData.noChessProgram = FALSE;
11046         appData.clockMode = TRUE;
11047         SetGNUMode();
11048         return 1;
11049 }
11050
11051 int
11052 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11053 {
11054     char buf[MSG_SIZ], *p, *q;
11055     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11056     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11057     skip = !all && group[0]; // if group requested, we start in skip mode
11058     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11059         p = names; q = buf; header = 0;
11060         while(*p && *p != '\n') *q++ = *p++;
11061         *q = 0;
11062         if(*p == '\n') p++;
11063         if(buf[0] == '#') {
11064             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11065             depth++; // we must be entering a new group
11066             if(all) continue; // suppress printing group headers when complete list requested
11067             header = 1;
11068             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11069         }
11070         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11071         if(engineList[i]) free(engineList[i]);
11072         engineList[i] = strdup(buf);
11073         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11074         if(engineMnemonic[i]) free(engineMnemonic[i]);
11075         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11076             strcat(buf, " (");
11077             sscanf(q + 8, "%s", buf + strlen(buf));
11078             strcat(buf, ")");
11079         }
11080         engineMnemonic[i] = strdup(buf);
11081         i++;
11082     }
11083     engineList[i] = engineMnemonic[i] = NULL;
11084     return i;
11085 }
11086
11087 // following implemented as macro to avoid type limitations
11088 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11089
11090 void
11091 SwapEngines (int n)
11092 {   // swap settings for first engine and other engine (so far only some selected options)
11093     int h;
11094     char *p;
11095     if(n == 0) return;
11096     SWAP(directory, p)
11097     SWAP(chessProgram, p)
11098     SWAP(isUCI, h)
11099     SWAP(hasOwnBookUCI, h)
11100     SWAP(protocolVersion, h)
11101     SWAP(reuse, h)
11102     SWAP(scoreIsAbsolute, h)
11103     SWAP(timeOdds, h)
11104     SWAP(logo, p)
11105     SWAP(pgnName, p)
11106     SWAP(pvSAN, h)
11107     SWAP(engOptions, p)
11108     SWAP(engInitString, p)
11109     SWAP(computerString, p)
11110     SWAP(features, p)
11111     SWAP(fenOverride, p)
11112     SWAP(NPS, h)
11113     SWAP(accumulateTC, h)
11114     SWAP(drawDepth, h)
11115     SWAP(host, p)
11116     SWAP(pseudo, h)
11117 }
11118
11119 int
11120 GetEngineLine (char *s, int n)
11121 {
11122     int i;
11123     char buf[MSG_SIZ];
11124     extern char *icsNames;
11125     if(!s || !*s) return 0;
11126     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11127     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11128     if(!mnemonic[i]) return 0;
11129     if(n == 11) return 1; // just testing if there was a match
11130     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11131     if(n == 1) SwapEngines(n);
11132     ParseArgsFromString(buf);
11133     if(n == 1) SwapEngines(n);
11134     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11135         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11136         ParseArgsFromString(buf);
11137     }
11138     return 1;
11139 }
11140
11141 int
11142 SetPlayer (int player, char *p)
11143 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11144     int i;
11145     char buf[MSG_SIZ], *engineName;
11146     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11147     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11148     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11149     if(mnemonic[i]) {
11150         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11151         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11152         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11153         ParseArgsFromString(buf);
11154     } else { // no engine with this nickname is installed!
11155         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11156         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11157         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11158         ModeHighlight();
11159         DisplayError(buf, 0);
11160         return 0;
11161     }
11162     free(engineName);
11163     return i;
11164 }
11165
11166 char *recentEngines;
11167
11168 void
11169 RecentEngineEvent (int nr)
11170 {
11171     int n;
11172 //    SwapEngines(1); // bump first to second
11173 //    ReplaceEngine(&second, 1); // and load it there
11174     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11175     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11176     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11177         ReplaceEngine(&first, 0);
11178         FloatToFront(&appData.recentEngineList, command[n]);
11179     }
11180 }
11181
11182 int
11183 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11184 {   // determine players from game number
11185     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11186
11187     if(appData.tourneyType == 0) {
11188         roundsPerCycle = (nPlayers - 1) | 1;
11189         pairingsPerRound = nPlayers / 2;
11190     } else if(appData.tourneyType > 0) {
11191         roundsPerCycle = nPlayers - appData.tourneyType;
11192         pairingsPerRound = appData.tourneyType;
11193     }
11194     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11195     gamesPerCycle = gamesPerRound * roundsPerCycle;
11196     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11197     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11198     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11199     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11200     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11201     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11202
11203     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11204     if(appData.roundSync) *syncInterval = gamesPerRound;
11205
11206     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11207
11208     if(appData.tourneyType == 0) {
11209         if(curPairing == (nPlayers-1)/2 ) {
11210             *whitePlayer = curRound;
11211             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11212         } else {
11213             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11214             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11215             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11216             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11217         }
11218     } else if(appData.tourneyType > 1) {
11219         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11220         *whitePlayer = curRound + appData.tourneyType;
11221     } else if(appData.tourneyType > 0) {
11222         *whitePlayer = curPairing;
11223         *blackPlayer = curRound + appData.tourneyType;
11224     }
11225
11226     // take care of white/black alternation per round.
11227     // For cycles and games this is already taken care of by default, derived from matchGame!
11228     return curRound & 1;
11229 }
11230
11231 int
11232 NextTourneyGame (int nr, int *swapColors)
11233 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11234     char *p, *q;
11235     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11236     FILE *tf;
11237     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11238     tf = fopen(appData.tourneyFile, "r");
11239     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11240     ParseArgsFromFile(tf); fclose(tf);
11241     InitTimeControls(); // TC might be altered from tourney file
11242
11243     nPlayers = CountPlayers(appData.participants); // count participants
11244     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11245     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11246
11247     if(syncInterval) {
11248         p = q = appData.results;
11249         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11250         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11251             DisplayMessage(_("Waiting for other game(s)"),"");
11252             waitingForGame = TRUE;
11253             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11254             return 0;
11255         }
11256         waitingForGame = FALSE;
11257     }
11258
11259     if(appData.tourneyType < 0) {
11260         if(nr>=0 && !pairingReceived) {
11261             char buf[1<<16];
11262             if(pairing.pr == NoProc) {
11263                 if(!appData.pairingEngine[0]) {
11264                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11265                     return 0;
11266                 }
11267                 StartChessProgram(&pairing); // starts the pairing engine
11268             }
11269             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11270             SendToProgram(buf, &pairing);
11271             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11272             SendToProgram(buf, &pairing);
11273             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11274         }
11275         pairingReceived = 0;                              // ... so we continue here
11276         *swapColors = 0;
11277         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11278         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11279         matchGame = 1; roundNr = nr / syncInterval + 1;
11280     }
11281
11282     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11283
11284     // redefine engines, engine dir, etc.
11285     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11286     if(first.pr == NoProc) {
11287       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11288       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11289     }
11290     if(second.pr == NoProc) {
11291       SwapEngines(1);
11292       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11293       SwapEngines(1);         // and make that valid for second engine by swapping
11294       InitEngine(&second, 1);
11295     }
11296     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11297     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11298     return OK;
11299 }
11300
11301 void
11302 NextMatchGame ()
11303 {   // performs game initialization that does not invoke engines, and then tries to start the game
11304     int res, firstWhite, swapColors = 0;
11305     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11306     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
11307         char buf[MSG_SIZ];
11308         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11309         if(strcmp(buf, currentDebugFile)) { // name has changed
11310             FILE *f = fopen(buf, "w");
11311             if(f) { // if opening the new file failed, just keep using the old one
11312                 ASSIGN(currentDebugFile, buf);
11313                 fclose(debugFP);
11314                 debugFP = f;
11315             }
11316             if(appData.serverFileName) {
11317                 if(serverFP) fclose(serverFP);
11318                 serverFP = fopen(appData.serverFileName, "w");
11319                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11320                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11321             }
11322         }
11323     }
11324     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11325     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11326     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11327     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11328     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11329     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11330     Reset(FALSE, first.pr != NoProc);
11331     res = LoadGameOrPosition(matchGame); // setup game
11332     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11333     if(!res) return; // abort when bad game/pos file
11334     TwoMachinesEvent();
11335 }
11336
11337 void
11338 UserAdjudicationEvent (int result)
11339 {
11340     ChessMove gameResult = GameIsDrawn;
11341
11342     if( result > 0 ) {
11343         gameResult = WhiteWins;
11344     }
11345     else if( result < 0 ) {
11346         gameResult = BlackWins;
11347     }
11348
11349     if( gameMode == TwoMachinesPlay ) {
11350         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11351     }
11352 }
11353
11354
11355 // [HGM] save: calculate checksum of game to make games easily identifiable
11356 int
11357 StringCheckSum (char *s)
11358 {
11359         int i = 0;
11360         if(s==NULL) return 0;
11361         while(*s) i = i*259 + *s++;
11362         return i;
11363 }
11364
11365 int
11366 GameCheckSum ()
11367 {
11368         int i, sum=0;
11369         for(i=backwardMostMove; i<forwardMostMove; i++) {
11370                 sum += pvInfoList[i].depth;
11371                 sum += StringCheckSum(parseList[i]);
11372                 sum += StringCheckSum(commentList[i]);
11373                 sum *= 261;
11374         }
11375         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11376         return sum + StringCheckSum(commentList[i]);
11377 } // end of save patch
11378
11379 void
11380 GameEnds (ChessMove result, char *resultDetails, int whosays)
11381 {
11382     GameMode nextGameMode;
11383     int isIcsGame;
11384     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11385
11386     if(endingGame) return; /* [HGM] crash: forbid recursion */
11387     endingGame = 1;
11388     if(twoBoards) { // [HGM] dual: switch back to one board
11389         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11390         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11391     }
11392     if (appData.debugMode) {
11393       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11394               result, resultDetails ? resultDetails : "(null)", whosays);
11395     }
11396
11397     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11398
11399     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11400
11401     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11402         /* If we are playing on ICS, the server decides when the
11403            game is over, but the engine can offer to draw, claim
11404            a draw, or resign.
11405          */
11406 #if ZIPPY
11407         if (appData.zippyPlay && first.initDone) {
11408             if (result == GameIsDrawn) {
11409                 /* In case draw still needs to be claimed */
11410                 SendToICS(ics_prefix);
11411                 SendToICS("draw\n");
11412             } else if (StrCaseStr(resultDetails, "resign")) {
11413                 SendToICS(ics_prefix);
11414                 SendToICS("resign\n");
11415             }
11416         }
11417 #endif
11418         endingGame = 0; /* [HGM] crash */
11419         return;
11420     }
11421
11422     /* If we're loading the game from a file, stop */
11423     if (whosays == GE_FILE) {
11424       (void) StopLoadGameTimer();
11425       gameFileFP = NULL;
11426     }
11427
11428     /* Cancel draw offers */
11429     first.offeredDraw = second.offeredDraw = 0;
11430
11431     /* If this is an ICS game, only ICS can really say it's done;
11432        if not, anyone can. */
11433     isIcsGame = (gameMode == IcsPlayingWhite ||
11434                  gameMode == IcsPlayingBlack ||
11435                  gameMode == IcsObserving    ||
11436                  gameMode == IcsExamining);
11437
11438     if (!isIcsGame || whosays == GE_ICS) {
11439         /* OK -- not an ICS game, or ICS said it was done */
11440         StopClocks();
11441         if (!isIcsGame && !appData.noChessProgram)
11442           SetUserThinkingEnables();
11443
11444         /* [HGM] if a machine claims the game end we verify this claim */
11445         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11446             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11447                 char claimer;
11448                 ChessMove trueResult = (ChessMove) -1;
11449
11450                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11451                                             first.twoMachinesColor[0] :
11452                                             second.twoMachinesColor[0] ;
11453
11454                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11455                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11456                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11457                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11458                 } else
11459                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11460                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11461                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11462                 } else
11463                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11464                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11465                 }
11466
11467                 // now verify win claims, but not in drop games, as we don't understand those yet
11468                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11469                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11470                     (result == WhiteWins && claimer == 'w' ||
11471                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11472                       if (appData.debugMode) {
11473                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11474                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11475                       }
11476                       if(result != trueResult) {
11477                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11478                               result = claimer == 'w' ? BlackWins : WhiteWins;
11479                               resultDetails = buf;
11480                       }
11481                 } else
11482                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11483                     && (forwardMostMove <= backwardMostMove ||
11484                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11485                         (claimer=='b')==(forwardMostMove&1))
11486                                                                                   ) {
11487                       /* [HGM] verify: draws that were not flagged are false claims */
11488                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11489                       result = claimer == 'w' ? BlackWins : WhiteWins;
11490                       resultDetails = buf;
11491                 }
11492                 /* (Claiming a loss is accepted no questions asked!) */
11493             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11494                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11495                 result = GameUnfinished;
11496                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11497             }
11498             /* [HGM] bare: don't allow bare King to win */
11499             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11500                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11501                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11502                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11503                && result != GameIsDrawn)
11504             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11505                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11506                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11507                         if(p >= 0 && p <= (int)WhiteKing) k++;
11508                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11509                 }
11510                 if (appData.debugMode) {
11511                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11512                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11513                 }
11514                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11515                         result = GameIsDrawn;
11516                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11517                         resultDetails = buf;
11518                 }
11519             }
11520         }
11521
11522
11523         if(serverMoves != NULL && !loadFlag) { char c = '=';
11524             if(result==WhiteWins) c = '+';
11525             if(result==BlackWins) c = '-';
11526             if(resultDetails != NULL)
11527                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11528         }
11529         if (resultDetails != NULL) {
11530             gameInfo.result = result;
11531             gameInfo.resultDetails = StrSave(resultDetails);
11532
11533             /* display last move only if game was not loaded from file */
11534             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11535                 DisplayMove(currentMove - 1);
11536
11537             if (forwardMostMove != 0) {
11538                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11539                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11540                                                                 ) {
11541                     if (*appData.saveGameFile != NULLCHAR) {
11542                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11543                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11544                         else
11545                         SaveGameToFile(appData.saveGameFile, TRUE);
11546                     } else if (appData.autoSaveGames) {
11547                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11548                     }
11549                     if (*appData.savePositionFile != NULLCHAR) {
11550                         SavePositionToFile(appData.savePositionFile);
11551                     }
11552                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11553                 }
11554             }
11555
11556             /* Tell program how game ended in case it is learning */
11557             /* [HGM] Moved this to after saving the PGN, just in case */
11558             /* engine died and we got here through time loss. In that */
11559             /* case we will get a fatal error writing the pipe, which */
11560             /* would otherwise lose us the PGN.                       */
11561             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11562             /* output during GameEnds should never be fatal anymore   */
11563             if (gameMode == MachinePlaysWhite ||
11564                 gameMode == MachinePlaysBlack ||
11565                 gameMode == TwoMachinesPlay ||
11566                 gameMode == IcsPlayingWhite ||
11567                 gameMode == IcsPlayingBlack ||
11568                 gameMode == BeginningOfGame) {
11569                 char buf[MSG_SIZ];
11570                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11571                         resultDetails);
11572                 if (first.pr != NoProc) {
11573                     SendToProgram(buf, &first);
11574                 }
11575                 if (second.pr != NoProc &&
11576                     gameMode == TwoMachinesPlay) {
11577                     SendToProgram(buf, &second);
11578                 }
11579             }
11580         }
11581
11582         if (appData.icsActive) {
11583             if (appData.quietPlay &&
11584                 (gameMode == IcsPlayingWhite ||
11585                  gameMode == IcsPlayingBlack)) {
11586                 SendToICS(ics_prefix);
11587                 SendToICS("set shout 1\n");
11588             }
11589             nextGameMode = IcsIdle;
11590             ics_user_moved = FALSE;
11591             /* clean up premove.  It's ugly when the game has ended and the
11592              * premove highlights are still on the board.
11593              */
11594             if (gotPremove) {
11595               gotPremove = FALSE;
11596               ClearPremoveHighlights();
11597               DrawPosition(FALSE, boards[currentMove]);
11598             }
11599             if (whosays == GE_ICS) {
11600                 switch (result) {
11601                 case WhiteWins:
11602                     if (gameMode == IcsPlayingWhite)
11603                         PlayIcsWinSound();
11604                     else if(gameMode == IcsPlayingBlack)
11605                         PlayIcsLossSound();
11606                     break;
11607                 case BlackWins:
11608                     if (gameMode == IcsPlayingBlack)
11609                         PlayIcsWinSound();
11610                     else if(gameMode == IcsPlayingWhite)
11611                         PlayIcsLossSound();
11612                     break;
11613                 case GameIsDrawn:
11614                     PlayIcsDrawSound();
11615                     break;
11616                 default:
11617                     PlayIcsUnfinishedSound();
11618                 }
11619             }
11620             if(appData.quitNext) { ExitEvent(0); return; }
11621         } else if (gameMode == EditGame ||
11622                    gameMode == PlayFromGameFile ||
11623                    gameMode == AnalyzeMode ||
11624                    gameMode == AnalyzeFile) {
11625             nextGameMode = gameMode;
11626         } else {
11627             nextGameMode = EndOfGame;
11628         }
11629         pausing = FALSE;
11630         ModeHighlight();
11631     } else {
11632         nextGameMode = gameMode;
11633     }
11634
11635     if (appData.noChessProgram) {
11636         gameMode = nextGameMode;
11637         ModeHighlight();
11638         endingGame = 0; /* [HGM] crash */
11639         return;
11640     }
11641
11642     if (first.reuse) {
11643         /* Put first chess program into idle state */
11644         if (first.pr != NoProc &&
11645             (gameMode == MachinePlaysWhite ||
11646              gameMode == MachinePlaysBlack ||
11647              gameMode == TwoMachinesPlay ||
11648              gameMode == IcsPlayingWhite ||
11649              gameMode == IcsPlayingBlack ||
11650              gameMode == BeginningOfGame)) {
11651             SendToProgram("force\n", &first);
11652             if (first.usePing) {
11653               char buf[MSG_SIZ];
11654               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11655               SendToProgram(buf, &first);
11656             }
11657         }
11658     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11659         /* Kill off first chess program */
11660         if (first.isr != NULL)
11661           RemoveInputSource(first.isr);
11662         first.isr = NULL;
11663
11664         if (first.pr != NoProc) {
11665             ExitAnalyzeMode();
11666             DoSleep( appData.delayBeforeQuit );
11667             SendToProgram("quit\n", &first);
11668             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11669             first.reload = TRUE;
11670         }
11671         first.pr = NoProc;
11672     }
11673     if (second.reuse) {
11674         /* Put second chess program into idle state */
11675         if (second.pr != NoProc &&
11676             gameMode == TwoMachinesPlay) {
11677             SendToProgram("force\n", &second);
11678             if (second.usePing) {
11679               char buf[MSG_SIZ];
11680               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11681               SendToProgram(buf, &second);
11682             }
11683         }
11684     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11685         /* Kill off second chess program */
11686         if (second.isr != NULL)
11687           RemoveInputSource(second.isr);
11688         second.isr = NULL;
11689
11690         if (second.pr != NoProc) {
11691             DoSleep( appData.delayBeforeQuit );
11692             SendToProgram("quit\n", &second);
11693             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11694             second.reload = TRUE;
11695         }
11696         second.pr = NoProc;
11697     }
11698
11699     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11700         char resChar = '=';
11701         switch (result) {
11702         case WhiteWins:
11703           resChar = '+';
11704           if (first.twoMachinesColor[0] == 'w') {
11705             first.matchWins++;
11706           } else {
11707             second.matchWins++;
11708           }
11709           break;
11710         case BlackWins:
11711           resChar = '-';
11712           if (first.twoMachinesColor[0] == 'b') {
11713             first.matchWins++;
11714           } else {
11715             second.matchWins++;
11716           }
11717           break;
11718         case GameUnfinished:
11719           resChar = ' ';
11720         default:
11721           break;
11722         }
11723
11724         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11725         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11726             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11727             ReserveGame(nextGame, resChar); // sets nextGame
11728             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11729             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11730         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11731
11732         if (nextGame <= appData.matchGames && !abortMatch) {
11733             gameMode = nextGameMode;
11734             matchGame = nextGame; // this will be overruled in tourney mode!
11735             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11736             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11737             endingGame = 0; /* [HGM] crash */
11738             return;
11739         } else {
11740             gameMode = nextGameMode;
11741             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11742                      first.tidy, second.tidy,
11743                      first.matchWins, second.matchWins,
11744                      appData.matchGames - (first.matchWins + second.matchWins));
11745             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11746             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11747             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11748             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11749                 first.twoMachinesColor = "black\n";
11750                 second.twoMachinesColor = "white\n";
11751             } else {
11752                 first.twoMachinesColor = "white\n";
11753                 second.twoMachinesColor = "black\n";
11754             }
11755         }
11756     }
11757     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11758         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11759       ExitAnalyzeMode();
11760     gameMode = nextGameMode;
11761     ModeHighlight();
11762     endingGame = 0;  /* [HGM] crash */
11763     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11764         if(matchMode == TRUE) { // match through command line: exit with or without popup
11765             if(ranking) {
11766                 ToNrEvent(forwardMostMove);
11767                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11768                 else ExitEvent(0);
11769             } else DisplayFatalError(buf, 0, 0);
11770         } else { // match through menu; just stop, with or without popup
11771             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11772             ModeHighlight();
11773             if(ranking){
11774                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11775             } else DisplayNote(buf);
11776       }
11777       if(ranking) free(ranking);
11778     }
11779 }
11780
11781 /* Assumes program was just initialized (initString sent).
11782    Leaves program in force mode. */
11783 void
11784 FeedMovesToProgram (ChessProgramState *cps, int upto)
11785 {
11786     int i;
11787
11788     if (appData.debugMode)
11789       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11790               startedFromSetupPosition ? "position and " : "",
11791               backwardMostMove, upto, cps->which);
11792     if(currentlyInitializedVariant != gameInfo.variant) {
11793       char buf[MSG_SIZ];
11794         // [HGM] variantswitch: make engine aware of new variant
11795         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11796                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11797                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11798         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11799         SendToProgram(buf, cps);
11800         currentlyInitializedVariant = gameInfo.variant;
11801     }
11802     SendToProgram("force\n", cps);
11803     if (startedFromSetupPosition) {
11804         SendBoard(cps, backwardMostMove);
11805     if (appData.debugMode) {
11806         fprintf(debugFP, "feedMoves\n");
11807     }
11808     }
11809     for (i = backwardMostMove; i < upto; i++) {
11810         SendMoveToProgram(i, cps);
11811     }
11812 }
11813
11814
11815 int
11816 ResurrectChessProgram ()
11817 {
11818      /* The chess program may have exited.
11819         If so, restart it and feed it all the moves made so far. */
11820     static int doInit = 0;
11821
11822     if (appData.noChessProgram) return 1;
11823
11824     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11825         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11826         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11827         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11828     } else {
11829         if (first.pr != NoProc) return 1;
11830         StartChessProgram(&first);
11831     }
11832     InitChessProgram(&first, FALSE);
11833     FeedMovesToProgram(&first, currentMove);
11834
11835     if (!first.sendTime) {
11836         /* can't tell gnuchess what its clock should read,
11837            so we bow to its notion. */
11838         ResetClocks();
11839         timeRemaining[0][currentMove] = whiteTimeRemaining;
11840         timeRemaining[1][currentMove] = blackTimeRemaining;
11841     }
11842
11843     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11844                 appData.icsEngineAnalyze) && first.analysisSupport) {
11845       SendToProgram("analyze\n", &first);
11846       first.analyzing = TRUE;
11847     }
11848     return 1;
11849 }
11850
11851 /*
11852  * Button procedures
11853  */
11854 void
11855 Reset (int redraw, int init)
11856 {
11857     int i;
11858
11859     if (appData.debugMode) {
11860         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11861                 redraw, init, gameMode);
11862     }
11863     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11864     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11865     CleanupTail(); // [HGM] vari: delete any stored variations
11866     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11867     pausing = pauseExamInvalid = FALSE;
11868     startedFromSetupPosition = blackPlaysFirst = FALSE;
11869     firstMove = TRUE;
11870     whiteFlag = blackFlag = FALSE;
11871     userOfferedDraw = FALSE;
11872     hintRequested = bookRequested = FALSE;
11873     first.maybeThinking = FALSE;
11874     second.maybeThinking = FALSE;
11875     first.bookSuspend = FALSE; // [HGM] book
11876     second.bookSuspend = FALSE;
11877     thinkOutput[0] = NULLCHAR;
11878     lastHint[0] = NULLCHAR;
11879     ClearGameInfo(&gameInfo);
11880     gameInfo.variant = StringToVariant(appData.variant);
11881     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11882     ics_user_moved = ics_clock_paused = FALSE;
11883     ics_getting_history = H_FALSE;
11884     ics_gamenum = -1;
11885     white_holding[0] = black_holding[0] = NULLCHAR;
11886     ClearProgramStats();
11887     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11888
11889     ResetFrontEnd();
11890     ClearHighlights();
11891     flipView = appData.flipView;
11892     ClearPremoveHighlights();
11893     gotPremove = FALSE;
11894     alarmSounded = FALSE;
11895     killX = killY = -1; // [HGM] lion
11896
11897     GameEnds(EndOfFile, NULL, GE_PLAYER);
11898     if(appData.serverMovesName != NULL) {
11899         /* [HGM] prepare to make moves file for broadcasting */
11900         clock_t t = clock();
11901         if(serverMoves != NULL) fclose(serverMoves);
11902         serverMoves = fopen(appData.serverMovesName, "r");
11903         if(serverMoves != NULL) {
11904             fclose(serverMoves);
11905             /* delay 15 sec before overwriting, so all clients can see end */
11906             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11907         }
11908         serverMoves = fopen(appData.serverMovesName, "w");
11909     }
11910
11911     ExitAnalyzeMode();
11912     gameMode = BeginningOfGame;
11913     ModeHighlight();
11914     if(appData.icsActive) gameInfo.variant = VariantNormal;
11915     currentMove = forwardMostMove = backwardMostMove = 0;
11916     MarkTargetSquares(1);
11917     InitPosition(redraw);
11918     for (i = 0; i < MAX_MOVES; i++) {
11919         if (commentList[i] != NULL) {
11920             free(commentList[i]);
11921             commentList[i] = NULL;
11922         }
11923     }
11924     ResetClocks();
11925     timeRemaining[0][0] = whiteTimeRemaining;
11926     timeRemaining[1][0] = blackTimeRemaining;
11927
11928     if (first.pr == NoProc) {
11929         StartChessProgram(&first);
11930     }
11931     if (init) {
11932             InitChessProgram(&first, startedFromSetupPosition);
11933     }
11934     DisplayTitle("");
11935     DisplayMessage("", "");
11936     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11937     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11938     ClearMap();        // [HGM] exclude: invalidate map
11939 }
11940
11941 void
11942 AutoPlayGameLoop ()
11943 {
11944     for (;;) {
11945         if (!AutoPlayOneMove())
11946           return;
11947         if (matchMode || appData.timeDelay == 0)
11948           continue;
11949         if (appData.timeDelay < 0)
11950           return;
11951         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11952         break;
11953     }
11954 }
11955
11956 void
11957 AnalyzeNextGame()
11958 {
11959     ReloadGame(1); // next game
11960 }
11961
11962 int
11963 AutoPlayOneMove ()
11964 {
11965     int fromX, fromY, toX, toY;
11966
11967     if (appData.debugMode) {
11968       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11969     }
11970
11971     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11972       return FALSE;
11973
11974     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11975       pvInfoList[currentMove].depth = programStats.depth;
11976       pvInfoList[currentMove].score = programStats.score;
11977       pvInfoList[currentMove].time  = 0;
11978       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11979       else { // append analysis of final position as comment
11980         char buf[MSG_SIZ];
11981         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11982         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11983       }
11984       programStats.depth = 0;
11985     }
11986
11987     if (currentMove >= forwardMostMove) {
11988       if(gameMode == AnalyzeFile) {
11989           if(appData.loadGameIndex == -1) {
11990             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11991           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11992           } else {
11993           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11994         }
11995       }
11996 //      gameMode = EndOfGame;
11997 //      ModeHighlight();
11998
11999       /* [AS] Clear current move marker at the end of a game */
12000       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12001
12002       return FALSE;
12003     }
12004
12005     toX = moveList[currentMove][2] - AAA;
12006     toY = moveList[currentMove][3] - ONE;
12007
12008     if (moveList[currentMove][1] == '@') {
12009         if (appData.highlightLastMove) {
12010             SetHighlights(-1, -1, toX, toY);
12011         }
12012     } else {
12013         int viaX = moveList[currentMove][5] - AAA;
12014         int viaY = moveList[currentMove][6] - ONE;
12015         fromX = moveList[currentMove][0] - AAA;
12016         fromY = moveList[currentMove][1] - ONE;
12017
12018         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12019
12020         if(moveList[currentMove][4] == ';') { // multi-leg
12021             ChessSquare piece = boards[currentMove][viaY][viaX];
12022             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12023             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12024             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12025             boards[currentMove][viaY][viaX] = piece;
12026         } else
12027         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12028
12029         if (appData.highlightLastMove) {
12030             SetHighlights(fromX, fromY, toX, toY);
12031         }
12032     }
12033     DisplayMove(currentMove);
12034     SendMoveToProgram(currentMove++, &first);
12035     DisplayBothClocks();
12036     DrawPosition(FALSE, boards[currentMove]);
12037     // [HGM] PV info: always display, routine tests if empty
12038     DisplayComment(currentMove - 1, commentList[currentMove]);
12039     return TRUE;
12040 }
12041
12042
12043 int
12044 LoadGameOneMove (ChessMove readAhead)
12045 {
12046     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12047     char promoChar = NULLCHAR;
12048     ChessMove moveType;
12049     char move[MSG_SIZ];
12050     char *p, *q;
12051
12052     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12053         gameMode != AnalyzeMode && gameMode != Training) {
12054         gameFileFP = NULL;
12055         return FALSE;
12056     }
12057
12058     yyboardindex = forwardMostMove;
12059     if (readAhead != EndOfFile) {
12060       moveType = readAhead;
12061     } else {
12062       if (gameFileFP == NULL)
12063           return FALSE;
12064       moveType = (ChessMove) Myylex();
12065     }
12066
12067     done = FALSE;
12068     switch (moveType) {
12069       case Comment:
12070         if (appData.debugMode)
12071           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12072         p = yy_text;
12073
12074         /* append the comment but don't display it */
12075         AppendComment(currentMove, p, FALSE);
12076         return TRUE;
12077
12078       case WhiteCapturesEnPassant:
12079       case BlackCapturesEnPassant:
12080       case WhitePromotion:
12081       case BlackPromotion:
12082       case WhiteNonPromotion:
12083       case BlackNonPromotion:
12084       case NormalMove:
12085       case FirstLeg:
12086       case WhiteKingSideCastle:
12087       case WhiteQueenSideCastle:
12088       case BlackKingSideCastle:
12089       case BlackQueenSideCastle:
12090       case WhiteKingSideCastleWild:
12091       case WhiteQueenSideCastleWild:
12092       case BlackKingSideCastleWild:
12093       case BlackQueenSideCastleWild:
12094       /* PUSH Fabien */
12095       case WhiteHSideCastleFR:
12096       case WhiteASideCastleFR:
12097       case BlackHSideCastleFR:
12098       case BlackASideCastleFR:
12099       /* POP Fabien */
12100         if (appData.debugMode)
12101           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12102         fromX = currentMoveString[0] - AAA;
12103         fromY = currentMoveString[1] - ONE;
12104         toX = currentMoveString[2] - AAA;
12105         toY = currentMoveString[3] - ONE;
12106         promoChar = currentMoveString[4];
12107         if(promoChar == ';') promoChar = NULLCHAR;
12108         break;
12109
12110       case WhiteDrop:
12111       case BlackDrop:
12112         if (appData.debugMode)
12113           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12114         fromX = moveType == WhiteDrop ?
12115           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12116         (int) CharToPiece(ToLower(currentMoveString[0]));
12117         fromY = DROP_RANK;
12118         toX = currentMoveString[2] - AAA;
12119         toY = currentMoveString[3] - ONE;
12120         break;
12121
12122       case WhiteWins:
12123       case BlackWins:
12124       case GameIsDrawn:
12125       case GameUnfinished:
12126         if (appData.debugMode)
12127           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12128         p = strchr(yy_text, '{');
12129         if (p == NULL) p = strchr(yy_text, '(');
12130         if (p == NULL) {
12131             p = yy_text;
12132             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12133         } else {
12134             q = strchr(p, *p == '{' ? '}' : ')');
12135             if (q != NULL) *q = NULLCHAR;
12136             p++;
12137         }
12138         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12139         GameEnds(moveType, p, GE_FILE);
12140         done = TRUE;
12141         if (cmailMsgLoaded) {
12142             ClearHighlights();
12143             flipView = WhiteOnMove(currentMove);
12144             if (moveType == GameUnfinished) flipView = !flipView;
12145             if (appData.debugMode)
12146               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12147         }
12148         break;
12149
12150       case EndOfFile:
12151         if (appData.debugMode)
12152           fprintf(debugFP, "Parser hit end of file\n");
12153         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12154           case MT_NONE:
12155           case MT_CHECK:
12156             break;
12157           case MT_CHECKMATE:
12158           case MT_STAINMATE:
12159             if (WhiteOnMove(currentMove)) {
12160                 GameEnds(BlackWins, "Black mates", GE_FILE);
12161             } else {
12162                 GameEnds(WhiteWins, "White mates", GE_FILE);
12163             }
12164             break;
12165           case MT_STALEMATE:
12166             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12167             break;
12168         }
12169         done = TRUE;
12170         break;
12171
12172       case MoveNumberOne:
12173         if (lastLoadGameStart == GNUChessGame) {
12174             /* GNUChessGames have numbers, but they aren't move numbers */
12175             if (appData.debugMode)
12176               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12177                       yy_text, (int) moveType);
12178             return LoadGameOneMove(EndOfFile); /* tail recursion */
12179         }
12180         /* else fall thru */
12181
12182       case XBoardGame:
12183       case GNUChessGame:
12184       case PGNTag:
12185         /* Reached start of next game in file */
12186         if (appData.debugMode)
12187           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12188         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12189           case MT_NONE:
12190           case MT_CHECK:
12191             break;
12192           case MT_CHECKMATE:
12193           case MT_STAINMATE:
12194             if (WhiteOnMove(currentMove)) {
12195                 GameEnds(BlackWins, "Black mates", GE_FILE);
12196             } else {
12197                 GameEnds(WhiteWins, "White mates", GE_FILE);
12198             }
12199             break;
12200           case MT_STALEMATE:
12201             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12202             break;
12203         }
12204         done = TRUE;
12205         break;
12206
12207       case PositionDiagram:     /* should not happen; ignore */
12208       case ElapsedTime:         /* ignore */
12209       case NAG:                 /* ignore */
12210         if (appData.debugMode)
12211           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12212                   yy_text, (int) moveType);
12213         return LoadGameOneMove(EndOfFile); /* tail recursion */
12214
12215       case IllegalMove:
12216         if (appData.testLegality) {
12217             if (appData.debugMode)
12218               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12219             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12220                     (forwardMostMove / 2) + 1,
12221                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12222             DisplayError(move, 0);
12223             done = TRUE;
12224         } else {
12225             if (appData.debugMode)
12226               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12227                       yy_text, currentMoveString);
12228             if(currentMoveString[1] == '@') {
12229                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12230                 fromY = DROP_RANK;
12231             } else {
12232                 fromX = currentMoveString[0] - AAA;
12233                 fromY = currentMoveString[1] - ONE;
12234             }
12235             toX = currentMoveString[2] - AAA;
12236             toY = currentMoveString[3] - ONE;
12237             promoChar = currentMoveString[4];
12238         }
12239         break;
12240
12241       case AmbiguousMove:
12242         if (appData.debugMode)
12243           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12244         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12245                 (forwardMostMove / 2) + 1,
12246                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12247         DisplayError(move, 0);
12248         done = TRUE;
12249         break;
12250
12251       default:
12252       case ImpossibleMove:
12253         if (appData.debugMode)
12254           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12255         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12256                 (forwardMostMove / 2) + 1,
12257                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12258         DisplayError(move, 0);
12259         done = TRUE;
12260         break;
12261     }
12262
12263     if (done) {
12264         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12265             DrawPosition(FALSE, boards[currentMove]);
12266             DisplayBothClocks();
12267             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12268               DisplayComment(currentMove - 1, commentList[currentMove]);
12269         }
12270         (void) StopLoadGameTimer();
12271         gameFileFP = NULL;
12272         cmailOldMove = forwardMostMove;
12273         return FALSE;
12274     } else {
12275         /* currentMoveString is set as a side-effect of yylex */
12276
12277         thinkOutput[0] = NULLCHAR;
12278         MakeMove(fromX, fromY, toX, toY, promoChar);
12279         killX = killY = -1; // [HGM] lion: used up
12280         currentMove = forwardMostMove;
12281         return TRUE;
12282     }
12283 }
12284
12285 /* Load the nth game from the given file */
12286 int
12287 LoadGameFromFile (char *filename, int n, char *title, int useList)
12288 {
12289     FILE *f;
12290     char buf[MSG_SIZ];
12291
12292     if (strcmp(filename, "-") == 0) {
12293         f = stdin;
12294         title = "stdin";
12295     } else {
12296         f = fopen(filename, "rb");
12297         if (f == NULL) {
12298           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12299             DisplayError(buf, errno);
12300             return FALSE;
12301         }
12302     }
12303     if (fseek(f, 0, 0) == -1) {
12304         /* f is not seekable; probably a pipe */
12305         useList = FALSE;
12306     }
12307     if (useList && n == 0) {
12308         int error = GameListBuild(f);
12309         if (error) {
12310             DisplayError(_("Cannot build game list"), error);
12311         } else if (!ListEmpty(&gameList) &&
12312                    ((ListGame *) gameList.tailPred)->number > 1) {
12313             GameListPopUp(f, title);
12314             return TRUE;
12315         }
12316         GameListDestroy();
12317         n = 1;
12318     }
12319     if (n == 0) n = 1;
12320     return LoadGame(f, n, title, FALSE);
12321 }
12322
12323
12324 void
12325 MakeRegisteredMove ()
12326 {
12327     int fromX, fromY, toX, toY;
12328     char promoChar;
12329     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12330         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12331           case CMAIL_MOVE:
12332           case CMAIL_DRAW:
12333             if (appData.debugMode)
12334               fprintf(debugFP, "Restoring %s for game %d\n",
12335                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12336
12337             thinkOutput[0] = NULLCHAR;
12338             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12339             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12340             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12341             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12342             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12343             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12344             MakeMove(fromX, fromY, toX, toY, promoChar);
12345             ShowMove(fromX, fromY, toX, toY);
12346
12347             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12348               case MT_NONE:
12349               case MT_CHECK:
12350                 break;
12351
12352               case MT_CHECKMATE:
12353               case MT_STAINMATE:
12354                 if (WhiteOnMove(currentMove)) {
12355                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12356                 } else {
12357                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12358                 }
12359                 break;
12360
12361               case MT_STALEMATE:
12362                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12363                 break;
12364             }
12365
12366             break;
12367
12368           case CMAIL_RESIGN:
12369             if (WhiteOnMove(currentMove)) {
12370                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12371             } else {
12372                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12373             }
12374             break;
12375
12376           case CMAIL_ACCEPT:
12377             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12378             break;
12379
12380           default:
12381             break;
12382         }
12383     }
12384
12385     return;
12386 }
12387
12388 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12389 int
12390 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12391 {
12392     int retVal;
12393
12394     if (gameNumber > nCmailGames) {
12395         DisplayError(_("No more games in this message"), 0);
12396         return FALSE;
12397     }
12398     if (f == lastLoadGameFP) {
12399         int offset = gameNumber - lastLoadGameNumber;
12400         if (offset == 0) {
12401             cmailMsg[0] = NULLCHAR;
12402             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12403                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12404                 nCmailMovesRegistered--;
12405             }
12406             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12407             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12408                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12409             }
12410         } else {
12411             if (! RegisterMove()) return FALSE;
12412         }
12413     }
12414
12415     retVal = LoadGame(f, gameNumber, title, useList);
12416
12417     /* Make move registered during previous look at this game, if any */
12418     MakeRegisteredMove();
12419
12420     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12421         commentList[currentMove]
12422           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12423         DisplayComment(currentMove - 1, commentList[currentMove]);
12424     }
12425
12426     return retVal;
12427 }
12428
12429 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12430 int
12431 ReloadGame (int offset)
12432 {
12433     int gameNumber = lastLoadGameNumber + offset;
12434     if (lastLoadGameFP == NULL) {
12435         DisplayError(_("No game has been loaded yet"), 0);
12436         return FALSE;
12437     }
12438     if (gameNumber <= 0) {
12439         DisplayError(_("Can't back up any further"), 0);
12440         return FALSE;
12441     }
12442     if (cmailMsgLoaded) {
12443         return CmailLoadGame(lastLoadGameFP, gameNumber,
12444                              lastLoadGameTitle, lastLoadGameUseList);
12445     } else {
12446         return LoadGame(lastLoadGameFP, gameNumber,
12447                         lastLoadGameTitle, lastLoadGameUseList);
12448     }
12449 }
12450
12451 int keys[EmptySquare+1];
12452
12453 int
12454 PositionMatches (Board b1, Board b2)
12455 {
12456     int r, f, sum=0;
12457     switch(appData.searchMode) {
12458         case 1: return CompareWithRights(b1, b2);
12459         case 2:
12460             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12461                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12462             }
12463             return TRUE;
12464         case 3:
12465             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12466               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12467                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12468             }
12469             return sum==0;
12470         case 4:
12471             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12472                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12473             }
12474             return sum==0;
12475     }
12476     return TRUE;
12477 }
12478
12479 #define Q_PROMO  4
12480 #define Q_EP     3
12481 #define Q_BCASTL 2
12482 #define Q_WCASTL 1
12483
12484 int pieceList[256], quickBoard[256];
12485 ChessSquare pieceType[256] = { EmptySquare };
12486 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12487 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12488 int soughtTotal, turn;
12489 Boolean epOK, flipSearch;
12490
12491 typedef struct {
12492     unsigned char piece, to;
12493 } Move;
12494
12495 #define DSIZE (250000)
12496
12497 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12498 Move *moveDatabase = initialSpace;
12499 unsigned int movePtr, dataSize = DSIZE;
12500
12501 int
12502 MakePieceList (Board board, int *counts)
12503 {
12504     int r, f, n=Q_PROMO, total=0;
12505     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12506     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12507         int sq = f + (r<<4);
12508         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12509             quickBoard[sq] = ++n;
12510             pieceList[n] = sq;
12511             pieceType[n] = board[r][f];
12512             counts[board[r][f]]++;
12513             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12514             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12515             total++;
12516         }
12517     }
12518     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12519     return total;
12520 }
12521
12522 void
12523 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12524 {
12525     int sq = fromX + (fromY<<4);
12526     int piece = quickBoard[sq], rook;
12527     quickBoard[sq] = 0;
12528     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12529     if(piece == pieceList[1] && fromY == toY) {
12530       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12531         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12532         moveDatabase[movePtr++].piece = Q_WCASTL;
12533         quickBoard[sq] = piece;
12534         piece = quickBoard[from]; quickBoard[from] = 0;
12535         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12536       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12537         quickBoard[sq] = 0; // remove Rook
12538         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12539         moveDatabase[movePtr++].piece = Q_WCASTL;
12540         quickBoard[sq] = pieceList[1]; // put King
12541         piece = rook;
12542         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12543       }
12544     } else
12545     if(piece == pieceList[2] && fromY == toY) {
12546       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12547         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12548         moveDatabase[movePtr++].piece = Q_BCASTL;
12549         quickBoard[sq] = piece;
12550         piece = quickBoard[from]; quickBoard[from] = 0;
12551         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12552       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12553         quickBoard[sq] = 0; // remove Rook
12554         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12555         moveDatabase[movePtr++].piece = Q_BCASTL;
12556         quickBoard[sq] = pieceList[2]; // put King
12557         piece = rook;
12558         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12559       }
12560     } else
12561     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12562         quickBoard[(fromY<<4)+toX] = 0;
12563         moveDatabase[movePtr].piece = Q_EP;
12564         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12565         moveDatabase[movePtr].to = sq;
12566     } else
12567     if(promoPiece != pieceType[piece]) {
12568         moveDatabase[movePtr++].piece = Q_PROMO;
12569         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12570     }
12571     moveDatabase[movePtr].piece = piece;
12572     quickBoard[sq] = piece;
12573     movePtr++;
12574 }
12575
12576 int
12577 PackGame (Board board)
12578 {
12579     Move *newSpace = NULL;
12580     moveDatabase[movePtr].piece = 0; // terminate previous game
12581     if(movePtr > dataSize) {
12582         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12583         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12584         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12585         if(newSpace) {
12586             int i;
12587             Move *p = moveDatabase, *q = newSpace;
12588             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12589             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12590             moveDatabase = newSpace;
12591         } else { // calloc failed, we must be out of memory. Too bad...
12592             dataSize = 0; // prevent calloc events for all subsequent games
12593             return 0;     // and signal this one isn't cached
12594         }
12595     }
12596     movePtr++;
12597     MakePieceList(board, counts);
12598     return movePtr;
12599 }
12600
12601 int
12602 QuickCompare (Board board, int *minCounts, int *maxCounts)
12603 {   // compare according to search mode
12604     int r, f;
12605     switch(appData.searchMode)
12606     {
12607       case 1: // exact position match
12608         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12609         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12610             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12611         }
12612         break;
12613       case 2: // can have extra material on empty squares
12614         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12615             if(board[r][f] == EmptySquare) continue;
12616             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12617         }
12618         break;
12619       case 3: // material with exact Pawn structure
12620         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12621             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12622             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12623         } // fall through to material comparison
12624       case 4: // exact material
12625         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12626         break;
12627       case 6: // material range with given imbalance
12628         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12629         // fall through to range comparison
12630       case 5: // material range
12631         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12632     }
12633     return TRUE;
12634 }
12635
12636 int
12637 QuickScan (Board board, Move *move)
12638 {   // reconstruct game,and compare all positions in it
12639     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12640     do {
12641         int piece = move->piece;
12642         int to = move->to, from = pieceList[piece];
12643         if(found < 0) { // if already found just scan to game end for final piece count
12644           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12645            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12646            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12647                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12648             ) {
12649             static int lastCounts[EmptySquare+1];
12650             int i;
12651             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12652             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12653           } else stretch = 0;
12654           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12655           if(found >= 0 && !appData.minPieces) return found;
12656         }
12657         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12658           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12659           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12660             piece = (++move)->piece;
12661             from = pieceList[piece];
12662             counts[pieceType[piece]]--;
12663             pieceType[piece] = (ChessSquare) move->to;
12664             counts[move->to]++;
12665           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12666             counts[pieceType[quickBoard[to]]]--;
12667             quickBoard[to] = 0; total--;
12668             move++;
12669             continue;
12670           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12671             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12672             from  = pieceList[piece]; // so this must be King
12673             quickBoard[from] = 0;
12674             pieceList[piece] = to;
12675             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12676             quickBoard[from] = 0; // rook
12677             quickBoard[to] = piece;
12678             to = move->to; piece = move->piece;
12679             goto aftercastle;
12680           }
12681         }
12682         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12683         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12684         quickBoard[from] = 0;
12685       aftercastle:
12686         quickBoard[to] = piece;
12687         pieceList[piece] = to;
12688         cnt++; turn ^= 3;
12689         move++;
12690     } while(1);
12691 }
12692
12693 void
12694 InitSearch ()
12695 {
12696     int r, f;
12697     flipSearch = FALSE;
12698     CopyBoard(soughtBoard, boards[currentMove]);
12699     soughtTotal = MakePieceList(soughtBoard, maxSought);
12700     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12701     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12702     CopyBoard(reverseBoard, boards[currentMove]);
12703     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12705         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12706         reverseBoard[r][f] = piece;
12707     }
12708     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12709     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12710     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12711                  || (boards[currentMove][CASTLING][2] == NoRights ||
12712                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12713                  && (boards[currentMove][CASTLING][5] == NoRights ||
12714                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12715       ) {
12716         flipSearch = TRUE;
12717         CopyBoard(flipBoard, soughtBoard);
12718         CopyBoard(rotateBoard, reverseBoard);
12719         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12720             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12721             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12722         }
12723     }
12724     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12725     if(appData.searchMode >= 5) {
12726         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12727         MakePieceList(soughtBoard, minSought);
12728         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12729     }
12730     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12731         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12732 }
12733
12734 GameInfo dummyInfo;
12735 static int creatingBook;
12736
12737 int
12738 GameContainsPosition (FILE *f, ListGame *lg)
12739 {
12740     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12741     int fromX, fromY, toX, toY;
12742     char promoChar;
12743     static int initDone=FALSE;
12744
12745     // weed out games based on numerical tag comparison
12746     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12747     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12748     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12749     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12750     if(!initDone) {
12751         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12752         initDone = TRUE;
12753     }
12754     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12755     else CopyBoard(boards[scratch], initialPosition); // default start position
12756     if(lg->moves) {
12757         turn = btm + 1;
12758         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12759         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12760     }
12761     if(btm) plyNr++;
12762     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12763     fseek(f, lg->offset, 0);
12764     yynewfile(f);
12765     while(1) {
12766         yyboardindex = scratch;
12767         quickFlag = plyNr+1;
12768         next = Myylex();
12769         quickFlag = 0;
12770         switch(next) {
12771             case PGNTag:
12772                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12773             default:
12774                 continue;
12775
12776             case XBoardGame:
12777             case GNUChessGame:
12778                 if(plyNr) return -1; // after we have seen moves, this is for new game
12779               continue;
12780
12781             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12782             case ImpossibleMove:
12783             case WhiteWins: // game ends here with these four
12784             case BlackWins:
12785             case GameIsDrawn:
12786             case GameUnfinished:
12787                 return -1;
12788
12789             case IllegalMove:
12790                 if(appData.testLegality) return -1;
12791             case WhiteCapturesEnPassant:
12792             case BlackCapturesEnPassant:
12793             case WhitePromotion:
12794             case BlackPromotion:
12795             case WhiteNonPromotion:
12796             case BlackNonPromotion:
12797             case NormalMove:
12798             case FirstLeg:
12799             case WhiteKingSideCastle:
12800             case WhiteQueenSideCastle:
12801             case BlackKingSideCastle:
12802             case BlackQueenSideCastle:
12803             case WhiteKingSideCastleWild:
12804             case WhiteQueenSideCastleWild:
12805             case BlackKingSideCastleWild:
12806             case BlackQueenSideCastleWild:
12807             case WhiteHSideCastleFR:
12808             case WhiteASideCastleFR:
12809             case BlackHSideCastleFR:
12810             case BlackASideCastleFR:
12811                 fromX = currentMoveString[0] - AAA;
12812                 fromY = currentMoveString[1] - ONE;
12813                 toX = currentMoveString[2] - AAA;
12814                 toY = currentMoveString[3] - ONE;
12815                 promoChar = currentMoveString[4];
12816                 break;
12817             case WhiteDrop:
12818             case BlackDrop:
12819                 fromX = next == WhiteDrop ?
12820                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12821                   (int) CharToPiece(ToLower(currentMoveString[0]));
12822                 fromY = DROP_RANK;
12823                 toX = currentMoveString[2] - AAA;
12824                 toY = currentMoveString[3] - ONE;
12825                 promoChar = 0;
12826                 break;
12827         }
12828         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12829         plyNr++;
12830         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12831         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12832         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12833         if(appData.findMirror) {
12834             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12835             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12836         }
12837     }
12838 }
12839
12840 /* Load the nth game from open file f */
12841 int
12842 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12843 {
12844     ChessMove cm;
12845     char buf[MSG_SIZ];
12846     int gn = gameNumber;
12847     ListGame *lg = NULL;
12848     int numPGNTags = 0;
12849     int err, pos = -1;
12850     GameMode oldGameMode;
12851     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12852     char oldName[MSG_SIZ];
12853
12854     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12855
12856     if (appData.debugMode)
12857         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12858
12859     if (gameMode == Training )
12860         SetTrainingModeOff();
12861
12862     oldGameMode = gameMode;
12863     if (gameMode != BeginningOfGame) {
12864       Reset(FALSE, TRUE);
12865     }
12866     killX = killY = -1; // [HGM] lion: in case we did not Reset
12867
12868     gameFileFP = f;
12869     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12870         fclose(lastLoadGameFP);
12871     }
12872
12873     if (useList) {
12874         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12875
12876         if (lg) {
12877             fseek(f, lg->offset, 0);
12878             GameListHighlight(gameNumber);
12879             pos = lg->position;
12880             gn = 1;
12881         }
12882         else {
12883             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12884               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12885             else
12886             DisplayError(_("Game number out of range"), 0);
12887             return FALSE;
12888         }
12889     } else {
12890         GameListDestroy();
12891         if (fseek(f, 0, 0) == -1) {
12892             if (f == lastLoadGameFP ?
12893                 gameNumber == lastLoadGameNumber + 1 :
12894                 gameNumber == 1) {
12895                 gn = 1;
12896             } else {
12897                 DisplayError(_("Can't seek on game file"), 0);
12898                 return FALSE;
12899             }
12900         }
12901     }
12902     lastLoadGameFP = f;
12903     lastLoadGameNumber = gameNumber;
12904     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12905     lastLoadGameUseList = useList;
12906
12907     yynewfile(f);
12908
12909     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12910       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12911                 lg->gameInfo.black);
12912             DisplayTitle(buf);
12913     } else if (*title != NULLCHAR) {
12914         if (gameNumber > 1) {
12915           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12916             DisplayTitle(buf);
12917         } else {
12918             DisplayTitle(title);
12919         }
12920     }
12921
12922     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12923         gameMode = PlayFromGameFile;
12924         ModeHighlight();
12925     }
12926
12927     currentMove = forwardMostMove = backwardMostMove = 0;
12928     CopyBoard(boards[0], initialPosition);
12929     StopClocks();
12930
12931     /*
12932      * Skip the first gn-1 games in the file.
12933      * Also skip over anything that precedes an identifiable
12934      * start of game marker, to avoid being confused by
12935      * garbage at the start of the file.  Currently
12936      * recognized start of game markers are the move number "1",
12937      * the pattern "gnuchess .* game", the pattern
12938      * "^[#;%] [^ ]* game file", and a PGN tag block.
12939      * A game that starts with one of the latter two patterns
12940      * will also have a move number 1, possibly
12941      * following a position diagram.
12942      * 5-4-02: Let's try being more lenient and allowing a game to
12943      * start with an unnumbered move.  Does that break anything?
12944      */
12945     cm = lastLoadGameStart = EndOfFile;
12946     while (gn > 0) {
12947         yyboardindex = forwardMostMove;
12948         cm = (ChessMove) Myylex();
12949         switch (cm) {
12950           case EndOfFile:
12951             if (cmailMsgLoaded) {
12952                 nCmailGames = CMAIL_MAX_GAMES - gn;
12953             } else {
12954                 Reset(TRUE, TRUE);
12955                 DisplayError(_("Game not found in file"), 0);
12956             }
12957             return FALSE;
12958
12959           case GNUChessGame:
12960           case XBoardGame:
12961             gn--;
12962             lastLoadGameStart = cm;
12963             break;
12964
12965           case MoveNumberOne:
12966             switch (lastLoadGameStart) {
12967               case GNUChessGame:
12968               case XBoardGame:
12969               case PGNTag:
12970                 break;
12971               case MoveNumberOne:
12972               case EndOfFile:
12973                 gn--;           /* count this game */
12974                 lastLoadGameStart = cm;
12975                 break;
12976               default:
12977                 /* impossible */
12978                 break;
12979             }
12980             break;
12981
12982           case PGNTag:
12983             switch (lastLoadGameStart) {
12984               case GNUChessGame:
12985               case PGNTag:
12986               case MoveNumberOne:
12987               case EndOfFile:
12988                 gn--;           /* count this game */
12989                 lastLoadGameStart = cm;
12990                 break;
12991               case XBoardGame:
12992                 lastLoadGameStart = cm; /* game counted already */
12993                 break;
12994               default:
12995                 /* impossible */
12996                 break;
12997             }
12998             if (gn > 0) {
12999                 do {
13000                     yyboardindex = forwardMostMove;
13001                     cm = (ChessMove) Myylex();
13002                 } while (cm == PGNTag || cm == Comment);
13003             }
13004             break;
13005
13006           case WhiteWins:
13007           case BlackWins:
13008           case GameIsDrawn:
13009             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13010                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13011                     != CMAIL_OLD_RESULT) {
13012                     nCmailResults ++ ;
13013                     cmailResult[  CMAIL_MAX_GAMES
13014                                 - gn - 1] = CMAIL_OLD_RESULT;
13015                 }
13016             }
13017             break;
13018
13019           case NormalMove:
13020           case FirstLeg:
13021             /* Only a NormalMove can be at the start of a game
13022              * without a position diagram. */
13023             if (lastLoadGameStart == EndOfFile ) {
13024               gn--;
13025               lastLoadGameStart = MoveNumberOne;
13026             }
13027             break;
13028
13029           default:
13030             break;
13031         }
13032     }
13033
13034     if (appData.debugMode)
13035       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13036
13037     if (cm == XBoardGame) {
13038         /* Skip any header junk before position diagram and/or move 1 */
13039         for (;;) {
13040             yyboardindex = forwardMostMove;
13041             cm = (ChessMove) Myylex();
13042
13043             if (cm == EndOfFile ||
13044                 cm == GNUChessGame || cm == XBoardGame) {
13045                 /* Empty game; pretend end-of-file and handle later */
13046                 cm = EndOfFile;
13047                 break;
13048             }
13049
13050             if (cm == MoveNumberOne || cm == PositionDiagram ||
13051                 cm == PGNTag || cm == Comment)
13052               break;
13053         }
13054     } else if (cm == GNUChessGame) {
13055         if (gameInfo.event != NULL) {
13056             free(gameInfo.event);
13057         }
13058         gameInfo.event = StrSave(yy_text);
13059     }
13060
13061     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13062     while (cm == PGNTag) {
13063         if (appData.debugMode)
13064           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13065         err = ParsePGNTag(yy_text, &gameInfo);
13066         if (!err) numPGNTags++;
13067
13068         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13069         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13070             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13071             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13072             InitPosition(TRUE);
13073             oldVariant = gameInfo.variant;
13074             if (appData.debugMode)
13075               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13076         }
13077
13078
13079         if (gameInfo.fen != NULL) {
13080           Board initial_position;
13081           startedFromSetupPosition = TRUE;
13082           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13083             Reset(TRUE, TRUE);
13084             DisplayError(_("Bad FEN position in file"), 0);
13085             return FALSE;
13086           }
13087           CopyBoard(boards[0], initial_position);
13088           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13089             CopyBoard(initialPosition, initial_position);
13090           if (blackPlaysFirst) {
13091             currentMove = forwardMostMove = backwardMostMove = 1;
13092             CopyBoard(boards[1], initial_position);
13093             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13094             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13095             timeRemaining[0][1] = whiteTimeRemaining;
13096             timeRemaining[1][1] = blackTimeRemaining;
13097             if (commentList[0] != NULL) {
13098               commentList[1] = commentList[0];
13099               commentList[0] = NULL;
13100             }
13101           } else {
13102             currentMove = forwardMostMove = backwardMostMove = 0;
13103           }
13104           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13105           {   int i;
13106               initialRulePlies = FENrulePlies;
13107               for( i=0; i< nrCastlingRights; i++ )
13108                   initialRights[i] = initial_position[CASTLING][i];
13109           }
13110           yyboardindex = forwardMostMove;
13111           free(gameInfo.fen);
13112           gameInfo.fen = NULL;
13113         }
13114
13115         yyboardindex = forwardMostMove;
13116         cm = (ChessMove) Myylex();
13117
13118         /* Handle comments interspersed among the tags */
13119         while (cm == Comment) {
13120             char *p;
13121             if (appData.debugMode)
13122               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13123             p = yy_text;
13124             AppendComment(currentMove, p, FALSE);
13125             yyboardindex = forwardMostMove;
13126             cm = (ChessMove) Myylex();
13127         }
13128     }
13129
13130     /* don't rely on existence of Event tag since if game was
13131      * pasted from clipboard the Event tag may not exist
13132      */
13133     if (numPGNTags > 0){
13134         char *tags;
13135         if (gameInfo.variant == VariantNormal) {
13136           VariantClass v = StringToVariant(gameInfo.event);
13137           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13138           if(v < VariantShogi) gameInfo.variant = v;
13139         }
13140         if (!matchMode) {
13141           if( appData.autoDisplayTags ) {
13142             tags = PGNTags(&gameInfo);
13143             TagsPopUp(tags, CmailMsg());
13144             free(tags);
13145           }
13146         }
13147     } else {
13148         /* Make something up, but don't display it now */
13149         SetGameInfo();
13150         TagsPopDown();
13151     }
13152
13153     if (cm == PositionDiagram) {
13154         int i, j;
13155         char *p;
13156         Board initial_position;
13157
13158         if (appData.debugMode)
13159           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13160
13161         if (!startedFromSetupPosition) {
13162             p = yy_text;
13163             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13164               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13165                 switch (*p) {
13166                   case '{':
13167                   case '[':
13168                   case '-':
13169                   case ' ':
13170                   case '\t':
13171                   case '\n':
13172                   case '\r':
13173                     break;
13174                   default:
13175                     initial_position[i][j++] = CharToPiece(*p);
13176                     break;
13177                 }
13178             while (*p == ' ' || *p == '\t' ||
13179                    *p == '\n' || *p == '\r') p++;
13180
13181             if (strncmp(p, "black", strlen("black"))==0)
13182               blackPlaysFirst = TRUE;
13183             else
13184               blackPlaysFirst = FALSE;
13185             startedFromSetupPosition = TRUE;
13186
13187             CopyBoard(boards[0], initial_position);
13188             if (blackPlaysFirst) {
13189                 currentMove = forwardMostMove = backwardMostMove = 1;
13190                 CopyBoard(boards[1], initial_position);
13191                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13192                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13193                 timeRemaining[0][1] = whiteTimeRemaining;
13194                 timeRemaining[1][1] = blackTimeRemaining;
13195                 if (commentList[0] != NULL) {
13196                     commentList[1] = commentList[0];
13197                     commentList[0] = NULL;
13198                 }
13199             } else {
13200                 currentMove = forwardMostMove = backwardMostMove = 0;
13201             }
13202         }
13203         yyboardindex = forwardMostMove;
13204         cm = (ChessMove) Myylex();
13205     }
13206
13207   if(!creatingBook) {
13208     if (first.pr == NoProc) {
13209         StartChessProgram(&first);
13210     }
13211     InitChessProgram(&first, FALSE);
13212     if(gameInfo.variant == VariantUnknown && *oldName) {
13213         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13214         gameInfo.variant = v;
13215     }
13216     SendToProgram("force\n", &first);
13217     if (startedFromSetupPosition) {
13218         SendBoard(&first, forwardMostMove);
13219     if (appData.debugMode) {
13220         fprintf(debugFP, "Load Game\n");
13221     }
13222         DisplayBothClocks();
13223     }
13224   }
13225
13226     /* [HGM] server: flag to write setup moves in broadcast file as one */
13227     loadFlag = appData.suppressLoadMoves;
13228
13229     while (cm == Comment) {
13230         char *p;
13231         if (appData.debugMode)
13232           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13233         p = yy_text;
13234         AppendComment(currentMove, p, FALSE);
13235         yyboardindex = forwardMostMove;
13236         cm = (ChessMove) Myylex();
13237     }
13238
13239     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13240         cm == WhiteWins || cm == BlackWins ||
13241         cm == GameIsDrawn || cm == GameUnfinished) {
13242         DisplayMessage("", _("No moves in game"));
13243         if (cmailMsgLoaded) {
13244             if (appData.debugMode)
13245               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13246             ClearHighlights();
13247             flipView = FALSE;
13248         }
13249         DrawPosition(FALSE, boards[currentMove]);
13250         DisplayBothClocks();
13251         gameMode = EditGame;
13252         ModeHighlight();
13253         gameFileFP = NULL;
13254         cmailOldMove = 0;
13255         return TRUE;
13256     }
13257
13258     // [HGM] PV info: routine tests if comment empty
13259     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13260         DisplayComment(currentMove - 1, commentList[currentMove]);
13261     }
13262     if (!matchMode && appData.timeDelay != 0)
13263       DrawPosition(FALSE, boards[currentMove]);
13264
13265     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13266       programStats.ok_to_send = 1;
13267     }
13268
13269     /* if the first token after the PGN tags is a move
13270      * and not move number 1, retrieve it from the parser
13271      */
13272     if (cm != MoveNumberOne)
13273         LoadGameOneMove(cm);
13274
13275     /* load the remaining moves from the file */
13276     while (LoadGameOneMove(EndOfFile)) {
13277       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13278       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13279     }
13280
13281     /* rewind to the start of the game */
13282     currentMove = backwardMostMove;
13283
13284     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13285
13286     if (oldGameMode == AnalyzeFile) {
13287       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13288       AnalyzeFileEvent();
13289     } else
13290     if (oldGameMode == AnalyzeMode) {
13291       AnalyzeFileEvent();
13292     }
13293
13294     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13295         long int w, b; // [HGM] adjourn: restore saved clock times
13296         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13297         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13298             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13299             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13300         }
13301     }
13302
13303     if(creatingBook) return TRUE;
13304     if (!matchMode && pos > 0) {
13305         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13306     } else
13307     if (matchMode || appData.timeDelay == 0) {
13308       ToEndEvent();
13309     } else if (appData.timeDelay > 0) {
13310       AutoPlayGameLoop();
13311     }
13312
13313     if (appData.debugMode)
13314         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13315
13316     loadFlag = 0; /* [HGM] true game starts */
13317     return TRUE;
13318 }
13319
13320 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13321 int
13322 ReloadPosition (int offset)
13323 {
13324     int positionNumber = lastLoadPositionNumber + offset;
13325     if (lastLoadPositionFP == NULL) {
13326         DisplayError(_("No position has been loaded yet"), 0);
13327         return FALSE;
13328     }
13329     if (positionNumber <= 0) {
13330         DisplayError(_("Can't back up any further"), 0);
13331         return FALSE;
13332     }
13333     return LoadPosition(lastLoadPositionFP, positionNumber,
13334                         lastLoadPositionTitle);
13335 }
13336
13337 /* Load the nth position from the given file */
13338 int
13339 LoadPositionFromFile (char *filename, int n, char *title)
13340 {
13341     FILE *f;
13342     char buf[MSG_SIZ];
13343
13344     if (strcmp(filename, "-") == 0) {
13345         return LoadPosition(stdin, n, "stdin");
13346     } else {
13347         f = fopen(filename, "rb");
13348         if (f == NULL) {
13349             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13350             DisplayError(buf, errno);
13351             return FALSE;
13352         } else {
13353             return LoadPosition(f, n, title);
13354         }
13355     }
13356 }
13357
13358 /* Load the nth position from the given open file, and close it */
13359 int
13360 LoadPosition (FILE *f, int positionNumber, char *title)
13361 {
13362     char *p, line[MSG_SIZ];
13363     Board initial_position;
13364     int i, j, fenMode, pn;
13365
13366     if (gameMode == Training )
13367         SetTrainingModeOff();
13368
13369     if (gameMode != BeginningOfGame) {
13370         Reset(FALSE, TRUE);
13371     }
13372     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13373         fclose(lastLoadPositionFP);
13374     }
13375     if (positionNumber == 0) positionNumber = 1;
13376     lastLoadPositionFP = f;
13377     lastLoadPositionNumber = positionNumber;
13378     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13379     if (first.pr == NoProc && !appData.noChessProgram) {
13380       StartChessProgram(&first);
13381       InitChessProgram(&first, FALSE);
13382     }
13383     pn = positionNumber;
13384     if (positionNumber < 0) {
13385         /* Negative position number means to seek to that byte offset */
13386         if (fseek(f, -positionNumber, 0) == -1) {
13387             DisplayError(_("Can't seek on position file"), 0);
13388             return FALSE;
13389         };
13390         pn = 1;
13391     } else {
13392         if (fseek(f, 0, 0) == -1) {
13393             if (f == lastLoadPositionFP ?
13394                 positionNumber == lastLoadPositionNumber + 1 :
13395                 positionNumber == 1) {
13396                 pn = 1;
13397             } else {
13398                 DisplayError(_("Can't seek on position file"), 0);
13399                 return FALSE;
13400             }
13401         }
13402     }
13403     /* See if this file is FEN or old-style xboard */
13404     if (fgets(line, MSG_SIZ, f) == NULL) {
13405         DisplayError(_("Position not found in file"), 0);
13406         return FALSE;
13407     }
13408     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13409     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13410
13411     if (pn >= 2) {
13412         if (fenMode || line[0] == '#') pn--;
13413         while (pn > 0) {
13414             /* skip positions before number pn */
13415             if (fgets(line, MSG_SIZ, f) == NULL) {
13416                 Reset(TRUE, TRUE);
13417                 DisplayError(_("Position not found in file"), 0);
13418                 return FALSE;
13419             }
13420             if (fenMode || line[0] == '#') pn--;
13421         }
13422     }
13423
13424     if (fenMode) {
13425         char *p;
13426         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13427             DisplayError(_("Bad FEN position in file"), 0);
13428             return FALSE;
13429         }
13430         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13431             sscanf(p+3, "%s", bestMove);
13432         } else *bestMove = NULLCHAR;
13433     } else {
13434         (void) fgets(line, MSG_SIZ, f);
13435         (void) fgets(line, MSG_SIZ, f);
13436
13437         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13438             (void) fgets(line, MSG_SIZ, f);
13439             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13440                 if (*p == ' ')
13441                   continue;
13442                 initial_position[i][j++] = CharToPiece(*p);
13443             }
13444         }
13445
13446         blackPlaysFirst = FALSE;
13447         if (!feof(f)) {
13448             (void) fgets(line, MSG_SIZ, f);
13449             if (strncmp(line, "black", strlen("black"))==0)
13450               blackPlaysFirst = TRUE;
13451         }
13452     }
13453     startedFromSetupPosition = TRUE;
13454
13455     CopyBoard(boards[0], initial_position);
13456     if (blackPlaysFirst) {
13457         currentMove = forwardMostMove = backwardMostMove = 1;
13458         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13459         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13460         CopyBoard(boards[1], initial_position);
13461         DisplayMessage("", _("Black to play"));
13462     } else {
13463         currentMove = forwardMostMove = backwardMostMove = 0;
13464         DisplayMessage("", _("White to play"));
13465     }
13466     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13467     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13468         SendToProgram("force\n", &first);
13469         SendBoard(&first, forwardMostMove);
13470     }
13471     if (appData.debugMode) {
13472 int i, j;
13473   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13474   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13475         fprintf(debugFP, "Load Position\n");
13476     }
13477
13478     if (positionNumber > 1) {
13479       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13480         DisplayTitle(line);
13481     } else {
13482         DisplayTitle(title);
13483     }
13484     gameMode = EditGame;
13485     ModeHighlight();
13486     ResetClocks();
13487     timeRemaining[0][1] = whiteTimeRemaining;
13488     timeRemaining[1][1] = blackTimeRemaining;
13489     DrawPosition(FALSE, boards[currentMove]);
13490
13491     return TRUE;
13492 }
13493
13494
13495 void
13496 CopyPlayerNameIntoFileName (char **dest, char *src)
13497 {
13498     while (*src != NULLCHAR && *src != ',') {
13499         if (*src == ' ') {
13500             *(*dest)++ = '_';
13501             src++;
13502         } else {
13503             *(*dest)++ = *src++;
13504         }
13505     }
13506 }
13507
13508 char *
13509 DefaultFileName (char *ext)
13510 {
13511     static char def[MSG_SIZ];
13512     char *p;
13513
13514     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13515         p = def;
13516         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13517         *p++ = '-';
13518         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13519         *p++ = '.';
13520         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13521     } else {
13522         def[0] = NULLCHAR;
13523     }
13524     return def;
13525 }
13526
13527 /* Save the current game to the given file */
13528 int
13529 SaveGameToFile (char *filename, int append)
13530 {
13531     FILE *f;
13532     char buf[MSG_SIZ];
13533     int result, i, t,tot=0;
13534
13535     if (strcmp(filename, "-") == 0) {
13536         return SaveGame(stdout, 0, NULL);
13537     } else {
13538         for(i=0; i<10; i++) { // upto 10 tries
13539              f = fopen(filename, append ? "a" : "w");
13540              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13541              if(f || errno != 13) break;
13542              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13543              tot += t;
13544         }
13545         if (f == NULL) {
13546             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13547             DisplayError(buf, errno);
13548             return FALSE;
13549         } else {
13550             safeStrCpy(buf, lastMsg, MSG_SIZ);
13551             DisplayMessage(_("Waiting for access to save file"), "");
13552             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13553             DisplayMessage(_("Saving game"), "");
13554             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13555             result = SaveGame(f, 0, NULL);
13556             DisplayMessage(buf, "");
13557             return result;
13558         }
13559     }
13560 }
13561
13562 char *
13563 SavePart (char *str)
13564 {
13565     static char buf[MSG_SIZ];
13566     char *p;
13567
13568     p = strchr(str, ' ');
13569     if (p == NULL) return str;
13570     strncpy(buf, str, p - str);
13571     buf[p - str] = NULLCHAR;
13572     return buf;
13573 }
13574
13575 #define PGN_MAX_LINE 75
13576
13577 #define PGN_SIDE_WHITE  0
13578 #define PGN_SIDE_BLACK  1
13579
13580 static int
13581 FindFirstMoveOutOfBook (int side)
13582 {
13583     int result = -1;
13584
13585     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13586         int index = backwardMostMove;
13587         int has_book_hit = 0;
13588
13589         if( (index % 2) != side ) {
13590             index++;
13591         }
13592
13593         while( index < forwardMostMove ) {
13594             /* Check to see if engine is in book */
13595             int depth = pvInfoList[index].depth;
13596             int score = pvInfoList[index].score;
13597             int in_book = 0;
13598
13599             if( depth <= 2 ) {
13600                 in_book = 1;
13601             }
13602             else if( score == 0 && depth == 63 ) {
13603                 in_book = 1; /* Zappa */
13604             }
13605             else if( score == 2 && depth == 99 ) {
13606                 in_book = 1; /* Abrok */
13607             }
13608
13609             has_book_hit += in_book;
13610
13611             if( ! in_book ) {
13612                 result = index;
13613
13614                 break;
13615             }
13616
13617             index += 2;
13618         }
13619     }
13620
13621     return result;
13622 }
13623
13624 void
13625 GetOutOfBookInfo (char * buf)
13626 {
13627     int oob[2];
13628     int i;
13629     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13630
13631     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13632     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13633
13634     *buf = '\0';
13635
13636     if( oob[0] >= 0 || oob[1] >= 0 ) {
13637         for( i=0; i<2; i++ ) {
13638             int idx = oob[i];
13639
13640             if( idx >= 0 ) {
13641                 if( i > 0 && oob[0] >= 0 ) {
13642                     strcat( buf, "   " );
13643                 }
13644
13645                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13646                 sprintf( buf+strlen(buf), "%s%.2f",
13647                     pvInfoList[idx].score >= 0 ? "+" : "",
13648                     pvInfoList[idx].score / 100.0 );
13649             }
13650         }
13651     }
13652 }
13653
13654 /* Save game in PGN style */
13655 static void
13656 SaveGamePGN2 (FILE *f)
13657 {
13658     int i, offset, linelen, newblock;
13659 //    char *movetext;
13660     char numtext[32];
13661     int movelen, numlen, blank;
13662     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13663
13664     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13665
13666     PrintPGNTags(f, &gameInfo);
13667
13668     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13669
13670     if (backwardMostMove > 0 || startedFromSetupPosition) {
13671         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13672         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13673         fprintf(f, "\n{--------------\n");
13674         PrintPosition(f, backwardMostMove);
13675         fprintf(f, "--------------}\n");
13676         free(fen);
13677     }
13678     else {
13679         /* [AS] Out of book annotation */
13680         if( appData.saveOutOfBookInfo ) {
13681             char buf[64];
13682
13683             GetOutOfBookInfo( buf );
13684
13685             if( buf[0] != '\0' ) {
13686                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13687             }
13688         }
13689
13690         fprintf(f, "\n");
13691     }
13692
13693     i = backwardMostMove;
13694     linelen = 0;
13695     newblock = TRUE;
13696
13697     while (i < forwardMostMove) {
13698         /* Print comments preceding this move */
13699         if (commentList[i] != NULL) {
13700             if (linelen > 0) fprintf(f, "\n");
13701             fprintf(f, "%s", commentList[i]);
13702             linelen = 0;
13703             newblock = TRUE;
13704         }
13705
13706         /* Format move number */
13707         if ((i % 2) == 0)
13708           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13709         else
13710           if (newblock)
13711             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13712           else
13713             numtext[0] = NULLCHAR;
13714
13715         numlen = strlen(numtext);
13716         newblock = FALSE;
13717
13718         /* Print move number */
13719         blank = linelen > 0 && numlen > 0;
13720         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13721             fprintf(f, "\n");
13722             linelen = 0;
13723             blank = 0;
13724         }
13725         if (blank) {
13726             fprintf(f, " ");
13727             linelen++;
13728         }
13729         fprintf(f, "%s", numtext);
13730         linelen += numlen;
13731
13732         /* Get move */
13733         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13734         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13735
13736         /* Print move */
13737         blank = linelen > 0 && movelen > 0;
13738         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13739             fprintf(f, "\n");
13740             linelen = 0;
13741             blank = 0;
13742         }
13743         if (blank) {
13744             fprintf(f, " ");
13745             linelen++;
13746         }
13747         fprintf(f, "%s", move_buffer);
13748         linelen += movelen;
13749
13750         /* [AS] Add PV info if present */
13751         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13752             /* [HGM] add time */
13753             char buf[MSG_SIZ]; int seconds;
13754
13755             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13756
13757             if( seconds <= 0)
13758               buf[0] = 0;
13759             else
13760               if( seconds < 30 )
13761                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13762               else
13763                 {
13764                   seconds = (seconds + 4)/10; // round to full seconds
13765                   if( seconds < 60 )
13766                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13767                   else
13768                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13769                 }
13770
13771             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13772                       pvInfoList[i].score >= 0 ? "+" : "",
13773                       pvInfoList[i].score / 100.0,
13774                       pvInfoList[i].depth,
13775                       buf );
13776
13777             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13778
13779             /* Print score/depth */
13780             blank = linelen > 0 && movelen > 0;
13781             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13782                 fprintf(f, "\n");
13783                 linelen = 0;
13784                 blank = 0;
13785             }
13786             if (blank) {
13787                 fprintf(f, " ");
13788                 linelen++;
13789             }
13790             fprintf(f, "%s", move_buffer);
13791             linelen += movelen;
13792         }
13793
13794         i++;
13795     }
13796
13797     /* Start a new line */
13798     if (linelen > 0) fprintf(f, "\n");
13799
13800     /* Print comments after last move */
13801     if (commentList[i] != NULL) {
13802         fprintf(f, "%s\n", commentList[i]);
13803     }
13804
13805     /* Print result */
13806     if (gameInfo.resultDetails != NULL &&
13807         gameInfo.resultDetails[0] != NULLCHAR) {
13808         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13809         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13810            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13811             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13812         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13813     } else {
13814         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13815     }
13816 }
13817
13818 /* Save game in PGN style and close the file */
13819 int
13820 SaveGamePGN (FILE *f)
13821 {
13822     SaveGamePGN2(f);
13823     fclose(f);
13824     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13825     return TRUE;
13826 }
13827
13828 /* Save game in old style and close the file */
13829 int
13830 SaveGameOldStyle (FILE *f)
13831 {
13832     int i, offset;
13833     time_t tm;
13834
13835     tm = time((time_t *) NULL);
13836
13837     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13838     PrintOpponents(f);
13839
13840     if (backwardMostMove > 0 || startedFromSetupPosition) {
13841         fprintf(f, "\n[--------------\n");
13842         PrintPosition(f, backwardMostMove);
13843         fprintf(f, "--------------]\n");
13844     } else {
13845         fprintf(f, "\n");
13846     }
13847
13848     i = backwardMostMove;
13849     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13850
13851     while (i < forwardMostMove) {
13852         if (commentList[i] != NULL) {
13853             fprintf(f, "[%s]\n", commentList[i]);
13854         }
13855
13856         if ((i % 2) == 1) {
13857             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13858             i++;
13859         } else {
13860             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13861             i++;
13862             if (commentList[i] != NULL) {
13863                 fprintf(f, "\n");
13864                 continue;
13865             }
13866             if (i >= forwardMostMove) {
13867                 fprintf(f, "\n");
13868                 break;
13869             }
13870             fprintf(f, "%s\n", parseList[i]);
13871             i++;
13872         }
13873     }
13874
13875     if (commentList[i] != NULL) {
13876         fprintf(f, "[%s]\n", commentList[i]);
13877     }
13878
13879     /* This isn't really the old style, but it's close enough */
13880     if (gameInfo.resultDetails != NULL &&
13881         gameInfo.resultDetails[0] != NULLCHAR) {
13882         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13883                 gameInfo.resultDetails);
13884     } else {
13885         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13886     }
13887
13888     fclose(f);
13889     return TRUE;
13890 }
13891
13892 /* Save the current game to open file f and close the file */
13893 int
13894 SaveGame (FILE *f, int dummy, char *dummy2)
13895 {
13896     if (gameMode == EditPosition) EditPositionDone(TRUE);
13897     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13898     if (appData.oldSaveStyle)
13899       return SaveGameOldStyle(f);
13900     else
13901       return SaveGamePGN(f);
13902 }
13903
13904 /* Save the current position to the given file */
13905 int
13906 SavePositionToFile (char *filename)
13907 {
13908     FILE *f;
13909     char buf[MSG_SIZ];
13910
13911     if (strcmp(filename, "-") == 0) {
13912         return SavePosition(stdout, 0, NULL);
13913     } else {
13914         f = fopen(filename, "a");
13915         if (f == NULL) {
13916             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13917             DisplayError(buf, errno);
13918             return FALSE;
13919         } else {
13920             safeStrCpy(buf, lastMsg, MSG_SIZ);
13921             DisplayMessage(_("Waiting for access to save file"), "");
13922             flock(fileno(f), LOCK_EX); // [HGM] lock
13923             DisplayMessage(_("Saving position"), "");
13924             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13925             SavePosition(f, 0, NULL);
13926             DisplayMessage(buf, "");
13927             return TRUE;
13928         }
13929     }
13930 }
13931
13932 /* Save the current position to the given open file and close the file */
13933 int
13934 SavePosition (FILE *f, int dummy, char *dummy2)
13935 {
13936     time_t tm;
13937     char *fen;
13938
13939     if (gameMode == EditPosition) EditPositionDone(TRUE);
13940     if (appData.oldSaveStyle) {
13941         tm = time((time_t *) NULL);
13942
13943         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13944         PrintOpponents(f);
13945         fprintf(f, "[--------------\n");
13946         PrintPosition(f, currentMove);
13947         fprintf(f, "--------------]\n");
13948     } else {
13949         fen = PositionToFEN(currentMove, NULL, 1);
13950         fprintf(f, "%s\n", fen);
13951         free(fen);
13952     }
13953     fclose(f);
13954     return TRUE;
13955 }
13956
13957 void
13958 ReloadCmailMsgEvent (int unregister)
13959 {
13960 #if !WIN32
13961     static char *inFilename = NULL;
13962     static char *outFilename;
13963     int i;
13964     struct stat inbuf, outbuf;
13965     int status;
13966
13967     /* Any registered moves are unregistered if unregister is set, */
13968     /* i.e. invoked by the signal handler */
13969     if (unregister) {
13970         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13971             cmailMoveRegistered[i] = FALSE;
13972             if (cmailCommentList[i] != NULL) {
13973                 free(cmailCommentList[i]);
13974                 cmailCommentList[i] = NULL;
13975             }
13976         }
13977         nCmailMovesRegistered = 0;
13978     }
13979
13980     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13981         cmailResult[i] = CMAIL_NOT_RESULT;
13982     }
13983     nCmailResults = 0;
13984
13985     if (inFilename == NULL) {
13986         /* Because the filenames are static they only get malloced once  */
13987         /* and they never get freed                                      */
13988         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13989         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13990
13991         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13992         sprintf(outFilename, "%s.out", appData.cmailGameName);
13993     }
13994
13995     status = stat(outFilename, &outbuf);
13996     if (status < 0) {
13997         cmailMailedMove = FALSE;
13998     } else {
13999         status = stat(inFilename, &inbuf);
14000         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14001     }
14002
14003     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14004        counts the games, notes how each one terminated, etc.
14005
14006        It would be nice to remove this kludge and instead gather all
14007        the information while building the game list.  (And to keep it
14008        in the game list nodes instead of having a bunch of fixed-size
14009        parallel arrays.)  Note this will require getting each game's
14010        termination from the PGN tags, as the game list builder does
14011        not process the game moves.  --mann
14012        */
14013     cmailMsgLoaded = TRUE;
14014     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14015
14016     /* Load first game in the file or popup game menu */
14017     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14018
14019 #endif /* !WIN32 */
14020     return;
14021 }
14022
14023 int
14024 RegisterMove ()
14025 {
14026     FILE *f;
14027     char string[MSG_SIZ];
14028
14029     if (   cmailMailedMove
14030         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14031         return TRUE;            /* Allow free viewing  */
14032     }
14033
14034     /* Unregister move to ensure that we don't leave RegisterMove        */
14035     /* with the move registered when the conditions for registering no   */
14036     /* longer hold                                                       */
14037     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14038         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14039         nCmailMovesRegistered --;
14040
14041         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14042           {
14043               free(cmailCommentList[lastLoadGameNumber - 1]);
14044               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14045           }
14046     }
14047
14048     if (cmailOldMove == -1) {
14049         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14050         return FALSE;
14051     }
14052
14053     if (currentMove > cmailOldMove + 1) {
14054         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14055         return FALSE;
14056     }
14057
14058     if (currentMove < cmailOldMove) {
14059         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14060         return FALSE;
14061     }
14062
14063     if (forwardMostMove > currentMove) {
14064         /* Silently truncate extra moves */
14065         TruncateGame();
14066     }
14067
14068     if (   (currentMove == cmailOldMove + 1)
14069         || (   (currentMove == cmailOldMove)
14070             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14071                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14072         if (gameInfo.result != GameUnfinished) {
14073             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14074         }
14075
14076         if (commentList[currentMove] != NULL) {
14077             cmailCommentList[lastLoadGameNumber - 1]
14078               = StrSave(commentList[currentMove]);
14079         }
14080         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14081
14082         if (appData.debugMode)
14083           fprintf(debugFP, "Saving %s for game %d\n",
14084                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14085
14086         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14087
14088         f = fopen(string, "w");
14089         if (appData.oldSaveStyle) {
14090             SaveGameOldStyle(f); /* also closes the file */
14091
14092             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14093             f = fopen(string, "w");
14094             SavePosition(f, 0, NULL); /* also closes the file */
14095         } else {
14096             fprintf(f, "{--------------\n");
14097             PrintPosition(f, currentMove);
14098             fprintf(f, "--------------}\n\n");
14099
14100             SaveGame(f, 0, NULL); /* also closes the file*/
14101         }
14102
14103         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14104         nCmailMovesRegistered ++;
14105     } else if (nCmailGames == 1) {
14106         DisplayError(_("You have not made a move yet"), 0);
14107         return FALSE;
14108     }
14109
14110     return TRUE;
14111 }
14112
14113 void
14114 MailMoveEvent ()
14115 {
14116 #if !WIN32
14117     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14118     FILE *commandOutput;
14119     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14120     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14121     int nBuffers;
14122     int i;
14123     int archived;
14124     char *arcDir;
14125
14126     if (! cmailMsgLoaded) {
14127         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14128         return;
14129     }
14130
14131     if (nCmailGames == nCmailResults) {
14132         DisplayError(_("No unfinished games"), 0);
14133         return;
14134     }
14135
14136 #if CMAIL_PROHIBIT_REMAIL
14137     if (cmailMailedMove) {
14138       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);
14139         DisplayError(msg, 0);
14140         return;
14141     }
14142 #endif
14143
14144     if (! (cmailMailedMove || RegisterMove())) return;
14145
14146     if (   cmailMailedMove
14147         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14148       snprintf(string, MSG_SIZ, partCommandString,
14149                appData.debugMode ? " -v" : "", appData.cmailGameName);
14150         commandOutput = popen(string, "r");
14151
14152         if (commandOutput == NULL) {
14153             DisplayError(_("Failed to invoke cmail"), 0);
14154         } else {
14155             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14156                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14157             }
14158             if (nBuffers > 1) {
14159                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14160                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14161                 nBytes = MSG_SIZ - 1;
14162             } else {
14163                 (void) memcpy(msg, buffer, nBytes);
14164             }
14165             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14166
14167             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14168                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14169
14170                 archived = TRUE;
14171                 for (i = 0; i < nCmailGames; i ++) {
14172                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14173                         archived = FALSE;
14174                     }
14175                 }
14176                 if (   archived
14177                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14178                         != NULL)) {
14179                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14180                            arcDir,
14181                            appData.cmailGameName,
14182                            gameInfo.date);
14183                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14184                     cmailMsgLoaded = FALSE;
14185                 }
14186             }
14187
14188             DisplayInformation(msg);
14189             pclose(commandOutput);
14190         }
14191     } else {
14192         if ((*cmailMsg) != '\0') {
14193             DisplayInformation(cmailMsg);
14194         }
14195     }
14196
14197     return;
14198 #endif /* !WIN32 */
14199 }
14200
14201 char *
14202 CmailMsg ()
14203 {
14204 #if WIN32
14205     return NULL;
14206 #else
14207     int  prependComma = 0;
14208     char number[5];
14209     char string[MSG_SIZ];       /* Space for game-list */
14210     int  i;
14211
14212     if (!cmailMsgLoaded) return "";
14213
14214     if (cmailMailedMove) {
14215       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14216     } else {
14217         /* Create a list of games left */
14218       snprintf(string, MSG_SIZ, "[");
14219         for (i = 0; i < nCmailGames; i ++) {
14220             if (! (   cmailMoveRegistered[i]
14221                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14222                 if (prependComma) {
14223                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14224                 } else {
14225                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14226                     prependComma = 1;
14227                 }
14228
14229                 strcat(string, number);
14230             }
14231         }
14232         strcat(string, "]");
14233
14234         if (nCmailMovesRegistered + nCmailResults == 0) {
14235             switch (nCmailGames) {
14236               case 1:
14237                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14238                 break;
14239
14240               case 2:
14241                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14242                 break;
14243
14244               default:
14245                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14246                          nCmailGames);
14247                 break;
14248             }
14249         } else {
14250             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14251               case 1:
14252                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14253                          string);
14254                 break;
14255
14256               case 0:
14257                 if (nCmailResults == nCmailGames) {
14258                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14259                 } else {
14260                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14261                 }
14262                 break;
14263
14264               default:
14265                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14266                          string);
14267             }
14268         }
14269     }
14270     return cmailMsg;
14271 #endif /* WIN32 */
14272 }
14273
14274 void
14275 ResetGameEvent ()
14276 {
14277     if (gameMode == Training)
14278       SetTrainingModeOff();
14279
14280     Reset(TRUE, TRUE);
14281     cmailMsgLoaded = FALSE;
14282     if (appData.icsActive) {
14283       SendToICS(ics_prefix);
14284       SendToICS("refresh\n");
14285     }
14286 }
14287
14288 void
14289 ExitEvent (int status)
14290 {
14291     exiting++;
14292     if (exiting > 2) {
14293       /* Give up on clean exit */
14294       exit(status);
14295     }
14296     if (exiting > 1) {
14297       /* Keep trying for clean exit */
14298       return;
14299     }
14300
14301     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14302     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14303
14304     if (telnetISR != NULL) {
14305       RemoveInputSource(telnetISR);
14306     }
14307     if (icsPR != NoProc) {
14308       DestroyChildProcess(icsPR, TRUE);
14309     }
14310
14311     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14312     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14313
14314     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14315     /* make sure this other one finishes before killing it!                  */
14316     if(endingGame) { int count = 0;
14317         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14318         while(endingGame && count++ < 10) DoSleep(1);
14319         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14320     }
14321
14322     /* Kill off chess programs */
14323     if (first.pr != NoProc) {
14324         ExitAnalyzeMode();
14325
14326         DoSleep( appData.delayBeforeQuit );
14327         SendToProgram("quit\n", &first);
14328         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14329     }
14330     if (second.pr != NoProc) {
14331         DoSleep( appData.delayBeforeQuit );
14332         SendToProgram("quit\n", &second);
14333         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14334     }
14335     if (first.isr != NULL) {
14336         RemoveInputSource(first.isr);
14337     }
14338     if (second.isr != NULL) {
14339         RemoveInputSource(second.isr);
14340     }
14341
14342     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14343     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14344
14345     ShutDownFrontEnd();
14346     exit(status);
14347 }
14348
14349 void
14350 PauseEngine (ChessProgramState *cps)
14351 {
14352     SendToProgram("pause\n", cps);
14353     cps->pause = 2;
14354 }
14355
14356 void
14357 UnPauseEngine (ChessProgramState *cps)
14358 {
14359     SendToProgram("resume\n", cps);
14360     cps->pause = 1;
14361 }
14362
14363 void
14364 PauseEvent ()
14365 {
14366     if (appData.debugMode)
14367         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14368     if (pausing) {
14369         pausing = FALSE;
14370         ModeHighlight();
14371         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14372             StartClocks();
14373             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14374                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14375                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14376             }
14377             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14378             HandleMachineMove(stashedInputMove, stalledEngine);
14379             stalledEngine = NULL;
14380             return;
14381         }
14382         if (gameMode == MachinePlaysWhite ||
14383             gameMode == TwoMachinesPlay   ||
14384             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14385             if(first.pause)  UnPauseEngine(&first);
14386             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14387             if(second.pause) UnPauseEngine(&second);
14388             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14389             StartClocks();
14390         } else {
14391             DisplayBothClocks();
14392         }
14393         if (gameMode == PlayFromGameFile) {
14394             if (appData.timeDelay >= 0)
14395                 AutoPlayGameLoop();
14396         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14397             Reset(FALSE, TRUE);
14398             SendToICS(ics_prefix);
14399             SendToICS("refresh\n");
14400         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14401             ForwardInner(forwardMostMove);
14402         }
14403         pauseExamInvalid = FALSE;
14404     } else {
14405         switch (gameMode) {
14406           default:
14407             return;
14408           case IcsExamining:
14409             pauseExamForwardMostMove = forwardMostMove;
14410             pauseExamInvalid = FALSE;
14411             /* fall through */
14412           case IcsObserving:
14413           case IcsPlayingWhite:
14414           case IcsPlayingBlack:
14415             pausing = TRUE;
14416             ModeHighlight();
14417             return;
14418           case PlayFromGameFile:
14419             (void) StopLoadGameTimer();
14420             pausing = TRUE;
14421             ModeHighlight();
14422             break;
14423           case BeginningOfGame:
14424             if (appData.icsActive) return;
14425             /* else fall through */
14426           case MachinePlaysWhite:
14427           case MachinePlaysBlack:
14428           case TwoMachinesPlay:
14429             if (forwardMostMove == 0)
14430               return;           /* don't pause if no one has moved */
14431             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14432                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14433                 if(onMove->pause) {           // thinking engine can be paused
14434                     PauseEngine(onMove);      // do it
14435                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14436                         PauseEngine(onMove->other);
14437                     else
14438                         SendToProgram("easy\n", onMove->other);
14439                     StopClocks();
14440                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14441             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14442                 if(first.pause) {
14443                     PauseEngine(&first);
14444                     StopClocks();
14445                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14446             } else { // human on move, pause pondering by either method
14447                 if(first.pause)
14448                     PauseEngine(&first);
14449                 else if(appData.ponderNextMove)
14450                     SendToProgram("easy\n", &first);
14451                 StopClocks();
14452             }
14453             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14454           case AnalyzeMode:
14455             pausing = TRUE;
14456             ModeHighlight();
14457             break;
14458         }
14459     }
14460 }
14461
14462 void
14463 EditCommentEvent ()
14464 {
14465     char title[MSG_SIZ];
14466
14467     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14468       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14469     } else {
14470       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14471                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14472                parseList[currentMove - 1]);
14473     }
14474
14475     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14476 }
14477
14478
14479 void
14480 EditTagsEvent ()
14481 {
14482     char *tags = PGNTags(&gameInfo);
14483     bookUp = FALSE;
14484     EditTagsPopUp(tags, NULL);
14485     free(tags);
14486 }
14487
14488 void
14489 ToggleSecond ()
14490 {
14491   if(second.analyzing) {
14492     SendToProgram("exit\n", &second);
14493     second.analyzing = FALSE;
14494   } else {
14495     if (second.pr == NoProc) StartChessProgram(&second);
14496     InitChessProgram(&second, FALSE);
14497     FeedMovesToProgram(&second, currentMove);
14498
14499     SendToProgram("analyze\n", &second);
14500     second.analyzing = TRUE;
14501   }
14502 }
14503
14504 /* Toggle ShowThinking */
14505 void
14506 ToggleShowThinking()
14507 {
14508   appData.showThinking = !appData.showThinking;
14509   ShowThinkingEvent();
14510 }
14511
14512 int
14513 AnalyzeModeEvent ()
14514 {
14515     char buf[MSG_SIZ];
14516
14517     if (!first.analysisSupport) {
14518       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14519       DisplayError(buf, 0);
14520       return 0;
14521     }
14522     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14523     if (appData.icsActive) {
14524         if (gameMode != IcsObserving) {
14525           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14526             DisplayError(buf, 0);
14527             /* secure check */
14528             if (appData.icsEngineAnalyze) {
14529                 if (appData.debugMode)
14530                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14531                 ExitAnalyzeMode();
14532                 ModeHighlight();
14533             }
14534             return 0;
14535         }
14536         /* if enable, user wants to disable icsEngineAnalyze */
14537         if (appData.icsEngineAnalyze) {
14538                 ExitAnalyzeMode();
14539                 ModeHighlight();
14540                 return 0;
14541         }
14542         appData.icsEngineAnalyze = TRUE;
14543         if (appData.debugMode)
14544             fprintf(debugFP, "ICS engine analyze starting... \n");
14545     }
14546
14547     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14548     if (appData.noChessProgram || gameMode == AnalyzeMode)
14549       return 0;
14550
14551     if (gameMode != AnalyzeFile) {
14552         if (!appData.icsEngineAnalyze) {
14553                EditGameEvent();
14554                if (gameMode != EditGame) return 0;
14555         }
14556         if (!appData.showThinking) ToggleShowThinking();
14557         ResurrectChessProgram();
14558         SendToProgram("analyze\n", &first);
14559         first.analyzing = TRUE;
14560         /*first.maybeThinking = TRUE;*/
14561         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14562         EngineOutputPopUp();
14563     }
14564     if (!appData.icsEngineAnalyze) {
14565         gameMode = AnalyzeMode;
14566         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14567     }
14568     pausing = FALSE;
14569     ModeHighlight();
14570     SetGameInfo();
14571
14572     StartAnalysisClock();
14573     GetTimeMark(&lastNodeCountTime);
14574     lastNodeCount = 0;
14575     return 1;
14576 }
14577
14578 void
14579 AnalyzeFileEvent ()
14580 {
14581     if (appData.noChessProgram || gameMode == AnalyzeFile)
14582       return;
14583
14584     if (!first.analysisSupport) {
14585       char buf[MSG_SIZ];
14586       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14587       DisplayError(buf, 0);
14588       return;
14589     }
14590
14591     if (gameMode != AnalyzeMode) {
14592         keepInfo = 1; // mere annotating should not alter PGN tags
14593         EditGameEvent();
14594         keepInfo = 0;
14595         if (gameMode != EditGame) return;
14596         if (!appData.showThinking) ToggleShowThinking();
14597         ResurrectChessProgram();
14598         SendToProgram("analyze\n", &first);
14599         first.analyzing = TRUE;
14600         /*first.maybeThinking = TRUE;*/
14601         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14602         EngineOutputPopUp();
14603     }
14604     gameMode = AnalyzeFile;
14605     pausing = FALSE;
14606     ModeHighlight();
14607
14608     StartAnalysisClock();
14609     GetTimeMark(&lastNodeCountTime);
14610     lastNodeCount = 0;
14611     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14612     AnalysisPeriodicEvent(1);
14613 }
14614
14615 void
14616 MachineWhiteEvent ()
14617 {
14618     char buf[MSG_SIZ];
14619     char *bookHit = NULL;
14620
14621     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14622       return;
14623
14624
14625     if (gameMode == PlayFromGameFile ||
14626         gameMode == TwoMachinesPlay  ||
14627         gameMode == Training         ||
14628         gameMode == AnalyzeMode      ||
14629         gameMode == EndOfGame)
14630         EditGameEvent();
14631
14632     if (gameMode == EditPosition)
14633         EditPositionDone(TRUE);
14634
14635     if (!WhiteOnMove(currentMove)) {
14636         DisplayError(_("It is not White's turn"), 0);
14637         return;
14638     }
14639
14640     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14641       ExitAnalyzeMode();
14642
14643     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14644         gameMode == AnalyzeFile)
14645         TruncateGame();
14646
14647     ResurrectChessProgram();    /* in case it isn't running */
14648     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14649         gameMode = MachinePlaysWhite;
14650         ResetClocks();
14651     } else
14652     gameMode = MachinePlaysWhite;
14653     pausing = FALSE;
14654     ModeHighlight();
14655     SetGameInfo();
14656     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14657     DisplayTitle(buf);
14658     if (first.sendName) {
14659       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14660       SendToProgram(buf, &first);
14661     }
14662     if (first.sendTime) {
14663       if (first.useColors) {
14664         SendToProgram("black\n", &first); /*gnu kludge*/
14665       }
14666       SendTimeRemaining(&first, TRUE);
14667     }
14668     if (first.useColors) {
14669       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14670     }
14671     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14672     SetMachineThinkingEnables();
14673     first.maybeThinking = TRUE;
14674     StartClocks();
14675     firstMove = FALSE;
14676
14677     if (appData.autoFlipView && !flipView) {
14678       flipView = !flipView;
14679       DrawPosition(FALSE, NULL);
14680       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14681     }
14682
14683     if(bookHit) { // [HGM] book: simulate book reply
14684         static char bookMove[MSG_SIZ]; // a bit generous?
14685
14686         programStats.nodes = programStats.depth = programStats.time =
14687         programStats.score = programStats.got_only_move = 0;
14688         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14689
14690         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14691         strcat(bookMove, bookHit);
14692         HandleMachineMove(bookMove, &first);
14693     }
14694 }
14695
14696 void
14697 MachineBlackEvent ()
14698 {
14699   char buf[MSG_SIZ];
14700   char *bookHit = NULL;
14701
14702     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14703         return;
14704
14705
14706     if (gameMode == PlayFromGameFile ||
14707         gameMode == TwoMachinesPlay  ||
14708         gameMode == Training         ||
14709         gameMode == AnalyzeMode      ||
14710         gameMode == EndOfGame)
14711         EditGameEvent();
14712
14713     if (gameMode == EditPosition)
14714         EditPositionDone(TRUE);
14715
14716     if (WhiteOnMove(currentMove)) {
14717         DisplayError(_("It is not Black's turn"), 0);
14718         return;
14719     }
14720
14721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14722       ExitAnalyzeMode();
14723
14724     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14725         gameMode == AnalyzeFile)
14726         TruncateGame();
14727
14728     ResurrectChessProgram();    /* in case it isn't running */
14729     gameMode = MachinePlaysBlack;
14730     pausing = FALSE;
14731     ModeHighlight();
14732     SetGameInfo();
14733     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14734     DisplayTitle(buf);
14735     if (first.sendName) {
14736       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14737       SendToProgram(buf, &first);
14738     }
14739     if (first.sendTime) {
14740       if (first.useColors) {
14741         SendToProgram("white\n", &first); /*gnu kludge*/
14742       }
14743       SendTimeRemaining(&first, FALSE);
14744     }
14745     if (first.useColors) {
14746       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14747     }
14748     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14749     SetMachineThinkingEnables();
14750     first.maybeThinking = TRUE;
14751     StartClocks();
14752
14753     if (appData.autoFlipView && flipView) {
14754       flipView = !flipView;
14755       DrawPosition(FALSE, NULL);
14756       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14757     }
14758     if(bookHit) { // [HGM] book: simulate book reply
14759         static char bookMove[MSG_SIZ]; // a bit generous?
14760
14761         programStats.nodes = programStats.depth = programStats.time =
14762         programStats.score = programStats.got_only_move = 0;
14763         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14764
14765         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14766         strcat(bookMove, bookHit);
14767         HandleMachineMove(bookMove, &first);
14768     }
14769 }
14770
14771
14772 void
14773 DisplayTwoMachinesTitle ()
14774 {
14775     char buf[MSG_SIZ];
14776     if (appData.matchGames > 0) {
14777         if(appData.tourneyFile[0]) {
14778           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14779                    gameInfo.white, _("vs."), gameInfo.black,
14780                    nextGame+1, appData.matchGames+1,
14781                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14782         } else
14783         if (first.twoMachinesColor[0] == 'w') {
14784           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14785                    gameInfo.white, _("vs."),  gameInfo.black,
14786                    first.matchWins, second.matchWins,
14787                    matchGame - 1 - (first.matchWins + second.matchWins));
14788         } else {
14789           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14790                    gameInfo.white, _("vs."), gameInfo.black,
14791                    second.matchWins, first.matchWins,
14792                    matchGame - 1 - (first.matchWins + second.matchWins));
14793         }
14794     } else {
14795       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14796     }
14797     DisplayTitle(buf);
14798 }
14799
14800 void
14801 SettingsMenuIfReady ()
14802 {
14803   if (second.lastPing != second.lastPong) {
14804     DisplayMessage("", _("Waiting for second chess program"));
14805     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14806     return;
14807   }
14808   ThawUI();
14809   DisplayMessage("", "");
14810   SettingsPopUp(&second);
14811 }
14812
14813 int
14814 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14815 {
14816     char buf[MSG_SIZ];
14817     if (cps->pr == NoProc) {
14818         StartChessProgram(cps);
14819         if (cps->protocolVersion == 1) {
14820           retry();
14821           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14822         } else {
14823           /* kludge: allow timeout for initial "feature" command */
14824           if(retry != TwoMachinesEventIfReady) FreezeUI();
14825           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14826           DisplayMessage("", buf);
14827           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14828         }
14829         return 1;
14830     }
14831     return 0;
14832 }
14833
14834 void
14835 TwoMachinesEvent P((void))
14836 {
14837     int i;
14838     char buf[MSG_SIZ];
14839     ChessProgramState *onmove;
14840     char *bookHit = NULL;
14841     static int stalling = 0;
14842     TimeMark now;
14843     long wait;
14844
14845     if (appData.noChessProgram) return;
14846
14847     switch (gameMode) {
14848       case TwoMachinesPlay:
14849         return;
14850       case MachinePlaysWhite:
14851       case MachinePlaysBlack:
14852         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14853             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14854             return;
14855         }
14856         /* fall through */
14857       case BeginningOfGame:
14858       case PlayFromGameFile:
14859       case EndOfGame:
14860         EditGameEvent();
14861         if (gameMode != EditGame) return;
14862         break;
14863       case EditPosition:
14864         EditPositionDone(TRUE);
14865         break;
14866       case AnalyzeMode:
14867       case AnalyzeFile:
14868         ExitAnalyzeMode();
14869         break;
14870       case EditGame:
14871       default:
14872         break;
14873     }
14874
14875 //    forwardMostMove = currentMove;
14876     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14877     startingEngine = TRUE;
14878
14879     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14880
14881     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14882     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14883       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14884       return;
14885     }
14886     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14887
14888     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14889                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14890         startingEngine = matchMode = FALSE;
14891         DisplayError("second engine does not play this", 0);
14892         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14893         EditGameEvent(); // switch back to EditGame mode
14894         return;
14895     }
14896
14897     if(!stalling) {
14898       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14899       SendToProgram("force\n", &second);
14900       stalling = 1;
14901       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14902       return;
14903     }
14904     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14905     if(appData.matchPause>10000 || appData.matchPause<10)
14906                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14907     wait = SubtractTimeMarks(&now, &pauseStart);
14908     if(wait < appData.matchPause) {
14909         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14910         return;
14911     }
14912     // we are now committed to starting the game
14913     stalling = 0;
14914     DisplayMessage("", "");
14915     if (startedFromSetupPosition) {
14916         SendBoard(&second, backwardMostMove);
14917     if (appData.debugMode) {
14918         fprintf(debugFP, "Two Machines\n");
14919     }
14920     }
14921     for (i = backwardMostMove; i < forwardMostMove; i++) {
14922         SendMoveToProgram(i, &second);
14923     }
14924
14925     gameMode = TwoMachinesPlay;
14926     pausing = startingEngine = FALSE;
14927     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14928     SetGameInfo();
14929     DisplayTwoMachinesTitle();
14930     firstMove = TRUE;
14931     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14932         onmove = &first;
14933     } else {
14934         onmove = &second;
14935     }
14936     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14937     SendToProgram(first.computerString, &first);
14938     if (first.sendName) {
14939       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14940       SendToProgram(buf, &first);
14941     }
14942     SendToProgram(second.computerString, &second);
14943     if (second.sendName) {
14944       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14945       SendToProgram(buf, &second);
14946     }
14947
14948     ResetClocks();
14949     if (!first.sendTime || !second.sendTime) {
14950         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14951         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14952     }
14953     if (onmove->sendTime) {
14954       if (onmove->useColors) {
14955         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14956       }
14957       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14958     }
14959     if (onmove->useColors) {
14960       SendToProgram(onmove->twoMachinesColor, onmove);
14961     }
14962     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14963 //    SendToProgram("go\n", onmove);
14964     onmove->maybeThinking = TRUE;
14965     SetMachineThinkingEnables();
14966
14967     StartClocks();
14968
14969     if(bookHit) { // [HGM] book: simulate book reply
14970         static char bookMove[MSG_SIZ]; // a bit generous?
14971
14972         programStats.nodes = programStats.depth = programStats.time =
14973         programStats.score = programStats.got_only_move = 0;
14974         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14975
14976         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14977         strcat(bookMove, bookHit);
14978         savedMessage = bookMove; // args for deferred call
14979         savedState = onmove;
14980         ScheduleDelayedEvent(DeferredBookMove, 1);
14981     }
14982 }
14983
14984 void
14985 TrainingEvent ()
14986 {
14987     if (gameMode == Training) {
14988       SetTrainingModeOff();
14989       gameMode = PlayFromGameFile;
14990       DisplayMessage("", _("Training mode off"));
14991     } else {
14992       gameMode = Training;
14993       animateTraining = appData.animate;
14994
14995       /* make sure we are not already at the end of the game */
14996       if (currentMove < forwardMostMove) {
14997         SetTrainingModeOn();
14998         DisplayMessage("", _("Training mode on"));
14999       } else {
15000         gameMode = PlayFromGameFile;
15001         DisplayError(_("Already at end of game"), 0);
15002       }
15003     }
15004     ModeHighlight();
15005 }
15006
15007 void
15008 IcsClientEvent ()
15009 {
15010     if (!appData.icsActive) return;
15011     switch (gameMode) {
15012       case IcsPlayingWhite:
15013       case IcsPlayingBlack:
15014       case IcsObserving:
15015       case IcsIdle:
15016       case BeginningOfGame:
15017       case IcsExamining:
15018         return;
15019
15020       case EditGame:
15021         break;
15022
15023       case EditPosition:
15024         EditPositionDone(TRUE);
15025         break;
15026
15027       case AnalyzeMode:
15028       case AnalyzeFile:
15029         ExitAnalyzeMode();
15030         break;
15031
15032       default:
15033         EditGameEvent();
15034         break;
15035     }
15036
15037     gameMode = IcsIdle;
15038     ModeHighlight();
15039     return;
15040 }
15041
15042 void
15043 EditGameEvent ()
15044 {
15045     int i;
15046
15047     switch (gameMode) {
15048       case Training:
15049         SetTrainingModeOff();
15050         break;
15051       case MachinePlaysWhite:
15052       case MachinePlaysBlack:
15053       case BeginningOfGame:
15054         SendToProgram("force\n", &first);
15055         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15056             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15057                 char buf[MSG_SIZ];
15058                 abortEngineThink = TRUE;
15059                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15060                 SendToProgram(buf, &first);
15061                 DisplayMessage("Aborting engine think", "");
15062                 FreezeUI();
15063             }
15064         }
15065         SetUserThinkingEnables();
15066         break;
15067       case PlayFromGameFile:
15068         (void) StopLoadGameTimer();
15069         if (gameFileFP != NULL) {
15070             gameFileFP = NULL;
15071         }
15072         break;
15073       case EditPosition:
15074         EditPositionDone(TRUE);
15075         break;
15076       case AnalyzeMode:
15077       case AnalyzeFile:
15078         ExitAnalyzeMode();
15079         SendToProgram("force\n", &first);
15080         break;
15081       case TwoMachinesPlay:
15082         GameEnds(EndOfFile, NULL, GE_PLAYER);
15083         ResurrectChessProgram();
15084         SetUserThinkingEnables();
15085         break;
15086       case EndOfGame:
15087         ResurrectChessProgram();
15088         break;
15089       case IcsPlayingBlack:
15090       case IcsPlayingWhite:
15091         DisplayError(_("Warning: You are still playing a game"), 0);
15092         break;
15093       case IcsObserving:
15094         DisplayError(_("Warning: You are still observing a game"), 0);
15095         break;
15096       case IcsExamining:
15097         DisplayError(_("Warning: You are still examining a game"), 0);
15098         break;
15099       case IcsIdle:
15100         break;
15101       case EditGame:
15102       default:
15103         return;
15104     }
15105
15106     pausing = FALSE;
15107     StopClocks();
15108     first.offeredDraw = second.offeredDraw = 0;
15109
15110     if (gameMode == PlayFromGameFile) {
15111         whiteTimeRemaining = timeRemaining[0][currentMove];
15112         blackTimeRemaining = timeRemaining[1][currentMove];
15113         DisplayTitle("");
15114     }
15115
15116     if (gameMode == MachinePlaysWhite ||
15117         gameMode == MachinePlaysBlack ||
15118         gameMode == TwoMachinesPlay ||
15119         gameMode == EndOfGame) {
15120         i = forwardMostMove;
15121         while (i > currentMove) {
15122             SendToProgram("undo\n", &first);
15123             i--;
15124         }
15125         if(!adjustedClock) {
15126         whiteTimeRemaining = timeRemaining[0][currentMove];
15127         blackTimeRemaining = timeRemaining[1][currentMove];
15128         DisplayBothClocks();
15129         }
15130         if (whiteFlag || blackFlag) {
15131             whiteFlag = blackFlag = 0;
15132         }
15133         DisplayTitle("");
15134     }
15135
15136     gameMode = EditGame;
15137     ModeHighlight();
15138     SetGameInfo();
15139 }
15140
15141
15142 void
15143 EditPositionEvent ()
15144 {
15145     if (gameMode == EditPosition) {
15146         EditGameEvent();
15147         return;
15148     }
15149
15150     EditGameEvent();
15151     if (gameMode != EditGame) return;
15152
15153     gameMode = EditPosition;
15154     ModeHighlight();
15155     SetGameInfo();
15156     if (currentMove > 0)
15157       CopyBoard(boards[0], boards[currentMove]);
15158
15159     blackPlaysFirst = !WhiteOnMove(currentMove);
15160     ResetClocks();
15161     currentMove = forwardMostMove = backwardMostMove = 0;
15162     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15163     DisplayMove(-1);
15164     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15165 }
15166
15167 void
15168 ExitAnalyzeMode ()
15169 {
15170     /* [DM] icsEngineAnalyze - possible call from other functions */
15171     if (appData.icsEngineAnalyze) {
15172         appData.icsEngineAnalyze = FALSE;
15173
15174         DisplayMessage("",_("Close ICS engine analyze..."));
15175     }
15176     if (first.analysisSupport && first.analyzing) {
15177       SendToBoth("exit\n");
15178       first.analyzing = second.analyzing = FALSE;
15179     }
15180     thinkOutput[0] = NULLCHAR;
15181 }
15182
15183 void
15184 EditPositionDone (Boolean fakeRights)
15185 {
15186     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15187
15188     startedFromSetupPosition = TRUE;
15189     InitChessProgram(&first, FALSE);
15190     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15191       boards[0][EP_STATUS] = EP_NONE;
15192       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15193       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15194         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15195         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15196       } else boards[0][CASTLING][2] = NoRights;
15197       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15198         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15199         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15200       } else boards[0][CASTLING][5] = NoRights;
15201       if(gameInfo.variant == VariantSChess) {
15202         int i;
15203         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15204           boards[0][VIRGIN][i] = 0;
15205           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15206           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15207         }
15208       }
15209     }
15210     SendToProgram("force\n", &first);
15211     if (blackPlaysFirst) {
15212         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15213         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15214         currentMove = forwardMostMove = backwardMostMove = 1;
15215         CopyBoard(boards[1], boards[0]);
15216     } else {
15217         currentMove = forwardMostMove = backwardMostMove = 0;
15218     }
15219     SendBoard(&first, forwardMostMove);
15220     if (appData.debugMode) {
15221         fprintf(debugFP, "EditPosDone\n");
15222     }
15223     DisplayTitle("");
15224     DisplayMessage("", "");
15225     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15226     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15227     gameMode = EditGame;
15228     ModeHighlight();
15229     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15230     ClearHighlights(); /* [AS] */
15231 }
15232
15233 /* Pause for `ms' milliseconds */
15234 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15235 void
15236 TimeDelay (long ms)
15237 {
15238     TimeMark m1, m2;
15239
15240     GetTimeMark(&m1);
15241     do {
15242         GetTimeMark(&m2);
15243     } while (SubtractTimeMarks(&m2, &m1) < ms);
15244 }
15245
15246 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15247 void
15248 SendMultiLineToICS (char *buf)
15249 {
15250     char temp[MSG_SIZ+1], *p;
15251     int len;
15252
15253     len = strlen(buf);
15254     if (len > MSG_SIZ)
15255       len = MSG_SIZ;
15256
15257     strncpy(temp, buf, len);
15258     temp[len] = 0;
15259
15260     p = temp;
15261     while (*p) {
15262         if (*p == '\n' || *p == '\r')
15263           *p = ' ';
15264         ++p;
15265     }
15266
15267     strcat(temp, "\n");
15268     SendToICS(temp);
15269     SendToPlayer(temp, strlen(temp));
15270 }
15271
15272 void
15273 SetWhiteToPlayEvent ()
15274 {
15275     if (gameMode == EditPosition) {
15276         blackPlaysFirst = FALSE;
15277         DisplayBothClocks();    /* works because currentMove is 0 */
15278     } else if (gameMode == IcsExamining) {
15279         SendToICS(ics_prefix);
15280         SendToICS("tomove white\n");
15281     }
15282 }
15283
15284 void
15285 SetBlackToPlayEvent ()
15286 {
15287     if (gameMode == EditPosition) {
15288         blackPlaysFirst = TRUE;
15289         currentMove = 1;        /* kludge */
15290         DisplayBothClocks();
15291         currentMove = 0;
15292     } else if (gameMode == IcsExamining) {
15293         SendToICS(ics_prefix);
15294         SendToICS("tomove black\n");
15295     }
15296 }
15297
15298 void
15299 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15300 {
15301     char buf[MSG_SIZ];
15302     ChessSquare piece = boards[0][y][x];
15303     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15304     static int lastVariant;
15305
15306     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15307
15308     switch (selection) {
15309       case ClearBoard:
15310         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15311         MarkTargetSquares(1);
15312         CopyBoard(currentBoard, boards[0]);
15313         CopyBoard(menuBoard, initialPosition);
15314         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15315             SendToICS(ics_prefix);
15316             SendToICS("bsetup clear\n");
15317         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15318             SendToICS(ics_prefix);
15319             SendToICS("clearboard\n");
15320         } else {
15321             int nonEmpty = 0;
15322             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15323                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15324                 for (y = 0; y < BOARD_HEIGHT; y++) {
15325                     if (gameMode == IcsExamining) {
15326                         if (boards[currentMove][y][x] != EmptySquare) {
15327                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15328                                     AAA + x, ONE + y);
15329                             SendToICS(buf);
15330                         }
15331                     } else if(boards[0][y][x] != DarkSquare) {
15332                         if(boards[0][y][x] != p) nonEmpty++;
15333                         boards[0][y][x] = p;
15334                     }
15335                 }
15336             }
15337             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15338                 int r;
15339                 for(r = 0; r < BOARD_HEIGHT; r++) {
15340                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15341                     ChessSquare p = menuBoard[r][x];
15342                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15343                   }
15344                 }
15345                 DisplayMessage("Clicking clock again restores position", "");
15346                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15347                 if(!nonEmpty) { // asked to clear an empty board
15348                     CopyBoard(boards[0], menuBoard);
15349                 } else
15350                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15351                     CopyBoard(boards[0], initialPosition);
15352                 } else
15353                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15354                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15355                     CopyBoard(boards[0], erasedBoard);
15356                 } else
15357                     CopyBoard(erasedBoard, currentBoard);
15358
15359             }
15360         }
15361         if (gameMode == EditPosition) {
15362             DrawPosition(FALSE, boards[0]);
15363         }
15364         break;
15365
15366       case WhitePlay:
15367         SetWhiteToPlayEvent();
15368         break;
15369
15370       case BlackPlay:
15371         SetBlackToPlayEvent();
15372         break;
15373
15374       case EmptySquare:
15375         if (gameMode == IcsExamining) {
15376             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15377             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15378             SendToICS(buf);
15379         } else {
15380             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15381                 if(x == BOARD_LEFT-2) {
15382                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15383                     boards[0][y][1] = 0;
15384                 } else
15385                 if(x == BOARD_RGHT+1) {
15386                     if(y >= gameInfo.holdingsSize) break;
15387                     boards[0][y][BOARD_WIDTH-2] = 0;
15388                 } else break;
15389             }
15390             boards[0][y][x] = EmptySquare;
15391             DrawPosition(FALSE, boards[0]);
15392         }
15393         break;
15394
15395       case PromotePiece:
15396         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15397            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15398             selection = (ChessSquare) (PROMOTED(piece));
15399         } else if(piece == EmptySquare) selection = WhiteSilver;
15400         else selection = (ChessSquare)((int)piece - 1);
15401         goto defaultlabel;
15402
15403       case DemotePiece:
15404         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15405            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15406             selection = (ChessSquare) (DEMOTED(piece));
15407         } else if(piece == EmptySquare) selection = BlackSilver;
15408         else selection = (ChessSquare)((int)piece + 1);
15409         goto defaultlabel;
15410
15411       case WhiteQueen:
15412       case BlackQueen:
15413         if(gameInfo.variant == VariantShatranj ||
15414            gameInfo.variant == VariantXiangqi  ||
15415            gameInfo.variant == VariantCourier  ||
15416            gameInfo.variant == VariantASEAN    ||
15417            gameInfo.variant == VariantMakruk     )
15418             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15419         goto defaultlabel;
15420
15421       case WhiteKing:
15422       case BlackKing:
15423         if(gameInfo.variant == VariantXiangqi)
15424             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15425         if(gameInfo.variant == VariantKnightmate)
15426             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15427       default:
15428         defaultlabel:
15429         if (gameMode == IcsExamining) {
15430             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15431             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15432                      PieceToChar(selection), AAA + x, ONE + y);
15433             SendToICS(buf);
15434         } else {
15435             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15436                 int n;
15437                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15438                     n = PieceToNumber(selection - BlackPawn);
15439                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15440                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15441                     boards[0][BOARD_HEIGHT-1-n][1]++;
15442                 } else
15443                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15444                     n = PieceToNumber(selection);
15445                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15446                     boards[0][n][BOARD_WIDTH-1] = selection;
15447                     boards[0][n][BOARD_WIDTH-2]++;
15448                 }
15449             } else
15450             boards[0][y][x] = selection;
15451             DrawPosition(TRUE, boards[0]);
15452             ClearHighlights();
15453             fromX = fromY = -1;
15454         }
15455         break;
15456     }
15457 }
15458
15459
15460 void
15461 DropMenuEvent (ChessSquare selection, int x, int y)
15462 {
15463     ChessMove moveType;
15464
15465     switch (gameMode) {
15466       case IcsPlayingWhite:
15467       case MachinePlaysBlack:
15468         if (!WhiteOnMove(currentMove)) {
15469             DisplayMoveError(_("It is Black's turn"));
15470             return;
15471         }
15472         moveType = WhiteDrop;
15473         break;
15474       case IcsPlayingBlack:
15475       case MachinePlaysWhite:
15476         if (WhiteOnMove(currentMove)) {
15477             DisplayMoveError(_("It is White's turn"));
15478             return;
15479         }
15480         moveType = BlackDrop;
15481         break;
15482       case EditGame:
15483         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15484         break;
15485       default:
15486         return;
15487     }
15488
15489     if (moveType == BlackDrop && selection < BlackPawn) {
15490       selection = (ChessSquare) ((int) selection
15491                                  + (int) BlackPawn - (int) WhitePawn);
15492     }
15493     if (boards[currentMove][y][x] != EmptySquare) {
15494         DisplayMoveError(_("That square is occupied"));
15495         return;
15496     }
15497
15498     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15499 }
15500
15501 void
15502 AcceptEvent ()
15503 {
15504     /* Accept a pending offer of any kind from opponent */
15505
15506     if (appData.icsActive) {
15507         SendToICS(ics_prefix);
15508         SendToICS("accept\n");
15509     } else if (cmailMsgLoaded) {
15510         if (currentMove == cmailOldMove &&
15511             commentList[cmailOldMove] != NULL &&
15512             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15513                    "Black offers a draw" : "White offers a draw")) {
15514             TruncateGame();
15515             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15516             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15517         } else {
15518             DisplayError(_("There is no pending offer on this move"), 0);
15519             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15520         }
15521     } else {
15522         /* Not used for offers from chess program */
15523     }
15524 }
15525
15526 void
15527 DeclineEvent ()
15528 {
15529     /* Decline a pending offer of any kind from opponent */
15530
15531     if (appData.icsActive) {
15532         SendToICS(ics_prefix);
15533         SendToICS("decline\n");
15534     } else if (cmailMsgLoaded) {
15535         if (currentMove == cmailOldMove &&
15536             commentList[cmailOldMove] != NULL &&
15537             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15538                    "Black offers a draw" : "White offers a draw")) {
15539 #ifdef NOTDEF
15540             AppendComment(cmailOldMove, "Draw declined", TRUE);
15541             DisplayComment(cmailOldMove - 1, "Draw declined");
15542 #endif /*NOTDEF*/
15543         } else {
15544             DisplayError(_("There is no pending offer on this move"), 0);
15545         }
15546     } else {
15547         /* Not used for offers from chess program */
15548     }
15549 }
15550
15551 void
15552 RematchEvent ()
15553 {
15554     /* Issue ICS rematch command */
15555     if (appData.icsActive) {
15556         SendToICS(ics_prefix);
15557         SendToICS("rematch\n");
15558     }
15559 }
15560
15561 void
15562 CallFlagEvent ()
15563 {
15564     /* Call your opponent's flag (claim a win on time) */
15565     if (appData.icsActive) {
15566         SendToICS(ics_prefix);
15567         SendToICS("flag\n");
15568     } else {
15569         switch (gameMode) {
15570           default:
15571             return;
15572           case MachinePlaysWhite:
15573             if (whiteFlag) {
15574                 if (blackFlag)
15575                   GameEnds(GameIsDrawn, "Both players ran out of time",
15576                            GE_PLAYER);
15577                 else
15578                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15579             } else {
15580                 DisplayError(_("Your opponent is not out of time"), 0);
15581             }
15582             break;
15583           case MachinePlaysBlack:
15584             if (blackFlag) {
15585                 if (whiteFlag)
15586                   GameEnds(GameIsDrawn, "Both players ran out of time",
15587                            GE_PLAYER);
15588                 else
15589                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15590             } else {
15591                 DisplayError(_("Your opponent is not out of time"), 0);
15592             }
15593             break;
15594         }
15595     }
15596 }
15597
15598 void
15599 ClockClick (int which)
15600 {       // [HGM] code moved to back-end from winboard.c
15601         if(which) { // black clock
15602           if (gameMode == EditPosition || gameMode == IcsExamining) {
15603             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15604             SetBlackToPlayEvent();
15605           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15606                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15607           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15608           } else if (shiftKey) {
15609             AdjustClock(which, -1);
15610           } else if (gameMode == IcsPlayingWhite ||
15611                      gameMode == MachinePlaysBlack) {
15612             CallFlagEvent();
15613           }
15614         } else { // white clock
15615           if (gameMode == EditPosition || gameMode == IcsExamining) {
15616             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15617             SetWhiteToPlayEvent();
15618           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15619                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15620           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15621           } else if (shiftKey) {
15622             AdjustClock(which, -1);
15623           } else if (gameMode == IcsPlayingBlack ||
15624                    gameMode == MachinePlaysWhite) {
15625             CallFlagEvent();
15626           }
15627         }
15628 }
15629
15630 void
15631 DrawEvent ()
15632 {
15633     /* Offer draw or accept pending draw offer from opponent */
15634
15635     if (appData.icsActive) {
15636         /* Note: tournament rules require draw offers to be
15637            made after you make your move but before you punch
15638            your clock.  Currently ICS doesn't let you do that;
15639            instead, you immediately punch your clock after making
15640            a move, but you can offer a draw at any time. */
15641
15642         SendToICS(ics_prefix);
15643         SendToICS("draw\n");
15644         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15645     } else if (cmailMsgLoaded) {
15646         if (currentMove == cmailOldMove &&
15647             commentList[cmailOldMove] != NULL &&
15648             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15649                    "Black offers a draw" : "White offers a draw")) {
15650             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15651             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15652         } else if (currentMove == cmailOldMove + 1) {
15653             char *offer = WhiteOnMove(cmailOldMove) ?
15654               "White offers a draw" : "Black offers a draw";
15655             AppendComment(currentMove, offer, TRUE);
15656             DisplayComment(currentMove - 1, offer);
15657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15658         } else {
15659             DisplayError(_("You must make your move before offering a draw"), 0);
15660             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15661         }
15662     } else if (first.offeredDraw) {
15663         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15664     } else {
15665         if (first.sendDrawOffers) {
15666             SendToProgram("draw\n", &first);
15667             userOfferedDraw = TRUE;
15668         }
15669     }
15670 }
15671
15672 void
15673 AdjournEvent ()
15674 {
15675     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15676
15677     if (appData.icsActive) {
15678         SendToICS(ics_prefix);
15679         SendToICS("adjourn\n");
15680     } else {
15681         /* Currently GNU Chess doesn't offer or accept Adjourns */
15682     }
15683 }
15684
15685
15686 void
15687 AbortEvent ()
15688 {
15689     /* Offer Abort or accept pending Abort offer from opponent */
15690
15691     if (appData.icsActive) {
15692         SendToICS(ics_prefix);
15693         SendToICS("abort\n");
15694     } else {
15695         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15696     }
15697 }
15698
15699 void
15700 ResignEvent ()
15701 {
15702     /* Resign.  You can do this even if it's not your turn. */
15703
15704     if (appData.icsActive) {
15705         SendToICS(ics_prefix);
15706         SendToICS("resign\n");
15707     } else {
15708         switch (gameMode) {
15709           case MachinePlaysWhite:
15710             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15711             break;
15712           case MachinePlaysBlack:
15713             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15714             break;
15715           case EditGame:
15716             if (cmailMsgLoaded) {
15717                 TruncateGame();
15718                 if (WhiteOnMove(cmailOldMove)) {
15719                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15720                 } else {
15721                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15722                 }
15723                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15724             }
15725             break;
15726           default:
15727             break;
15728         }
15729     }
15730 }
15731
15732
15733 void
15734 StopObservingEvent ()
15735 {
15736     /* Stop observing current games */
15737     SendToICS(ics_prefix);
15738     SendToICS("unobserve\n");
15739 }
15740
15741 void
15742 StopExaminingEvent ()
15743 {
15744     /* Stop observing current game */
15745     SendToICS(ics_prefix);
15746     SendToICS("unexamine\n");
15747 }
15748
15749 void
15750 ForwardInner (int target)
15751 {
15752     int limit; int oldSeekGraphUp = seekGraphUp;
15753
15754     if (appData.debugMode)
15755         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15756                 target, currentMove, forwardMostMove);
15757
15758     if (gameMode == EditPosition)
15759       return;
15760
15761     seekGraphUp = FALSE;
15762     MarkTargetSquares(1);
15763     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15764
15765     if (gameMode == PlayFromGameFile && !pausing)
15766       PauseEvent();
15767
15768     if (gameMode == IcsExamining && pausing)
15769       limit = pauseExamForwardMostMove;
15770     else
15771       limit = forwardMostMove;
15772
15773     if (target > limit) target = limit;
15774
15775     if (target > 0 && moveList[target - 1][0]) {
15776         int fromX, fromY, toX, toY;
15777         toX = moveList[target - 1][2] - AAA;
15778         toY = moveList[target - 1][3] - ONE;
15779         if (moveList[target - 1][1] == '@') {
15780             if (appData.highlightLastMove) {
15781                 SetHighlights(-1, -1, toX, toY);
15782             }
15783         } else {
15784             int viaX = moveList[target - 1][5] - AAA;
15785             int viaY = moveList[target - 1][6] - ONE;
15786             fromX = moveList[target - 1][0] - AAA;
15787             fromY = moveList[target - 1][1] - ONE;
15788             if (target == currentMove + 1) {
15789                 if(moveList[target - 1][4] == ';') { // multi-leg
15790                     ChessSquare piece = boards[currentMove][viaY][viaX];
15791                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15792                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15793                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15794                     boards[currentMove][viaY][viaX] = piece;
15795                 } else
15796                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15797             }
15798             if (appData.highlightLastMove) {
15799                 SetHighlights(fromX, fromY, toX, toY);
15800             }
15801         }
15802     }
15803     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15804         gameMode == Training || gameMode == PlayFromGameFile ||
15805         gameMode == AnalyzeFile) {
15806         while (currentMove < target) {
15807             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15808             SendMoveToProgram(currentMove++, &first);
15809         }
15810     } else {
15811         currentMove = target;
15812     }
15813
15814     if (gameMode == EditGame || gameMode == EndOfGame) {
15815         whiteTimeRemaining = timeRemaining[0][currentMove];
15816         blackTimeRemaining = timeRemaining[1][currentMove];
15817     }
15818     DisplayBothClocks();
15819     DisplayMove(currentMove - 1);
15820     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15821     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15822     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15823         DisplayComment(currentMove - 1, commentList[currentMove]);
15824     }
15825     ClearMap(); // [HGM] exclude: invalidate map
15826 }
15827
15828
15829 void
15830 ForwardEvent ()
15831 {
15832     if (gameMode == IcsExamining && !pausing) {
15833         SendToICS(ics_prefix);
15834         SendToICS("forward\n");
15835     } else {
15836         ForwardInner(currentMove + 1);
15837     }
15838 }
15839
15840 void
15841 ToEndEvent ()
15842 {
15843     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15844         /* to optimze, we temporarily turn off analysis mode while we feed
15845          * the remaining moves to the engine. Otherwise we get analysis output
15846          * after each move.
15847          */
15848         if (first.analysisSupport) {
15849           SendToProgram("exit\nforce\n", &first);
15850           first.analyzing = FALSE;
15851         }
15852     }
15853
15854     if (gameMode == IcsExamining && !pausing) {
15855         SendToICS(ics_prefix);
15856         SendToICS("forward 999999\n");
15857     } else {
15858         ForwardInner(forwardMostMove);
15859     }
15860
15861     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15862         /* we have fed all the moves, so reactivate analysis mode */
15863         SendToProgram("analyze\n", &first);
15864         first.analyzing = TRUE;
15865         /*first.maybeThinking = TRUE;*/
15866         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15867     }
15868 }
15869
15870 void
15871 BackwardInner (int target)
15872 {
15873     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15874
15875     if (appData.debugMode)
15876         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15877                 target, currentMove, forwardMostMove);
15878
15879     if (gameMode == EditPosition) return;
15880     seekGraphUp = FALSE;
15881     MarkTargetSquares(1);
15882     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15883     if (currentMove <= backwardMostMove) {
15884         ClearHighlights();
15885         DrawPosition(full_redraw, boards[currentMove]);
15886         return;
15887     }
15888     if (gameMode == PlayFromGameFile && !pausing)
15889       PauseEvent();
15890
15891     if (moveList[target][0]) {
15892         int fromX, fromY, toX, toY;
15893         toX = moveList[target][2] - AAA;
15894         toY = moveList[target][3] - ONE;
15895         if (moveList[target][1] == '@') {
15896             if (appData.highlightLastMove) {
15897                 SetHighlights(-1, -1, toX, toY);
15898             }
15899         } else {
15900             fromX = moveList[target][0] - AAA;
15901             fromY = moveList[target][1] - ONE;
15902             if (target == currentMove - 1) {
15903                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15904             }
15905             if (appData.highlightLastMove) {
15906                 SetHighlights(fromX, fromY, toX, toY);
15907             }
15908         }
15909     }
15910     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15911         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15912         while (currentMove > target) {
15913             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15914                 // null move cannot be undone. Reload program with move history before it.
15915                 int i;
15916                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15917                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15918                 }
15919                 SendBoard(&first, i);
15920               if(second.analyzing) SendBoard(&second, i);
15921                 for(currentMove=i; currentMove<target; currentMove++) {
15922                     SendMoveToProgram(currentMove, &first);
15923                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15924                 }
15925                 break;
15926             }
15927             SendToBoth("undo\n");
15928             currentMove--;
15929         }
15930     } else {
15931         currentMove = target;
15932     }
15933
15934     if (gameMode == EditGame || gameMode == EndOfGame) {
15935         whiteTimeRemaining = timeRemaining[0][currentMove];
15936         blackTimeRemaining = timeRemaining[1][currentMove];
15937     }
15938     DisplayBothClocks();
15939     DisplayMove(currentMove - 1);
15940     DrawPosition(full_redraw, boards[currentMove]);
15941     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15942     // [HGM] PV info: routine tests if comment empty
15943     DisplayComment(currentMove - 1, commentList[currentMove]);
15944     ClearMap(); // [HGM] exclude: invalidate map
15945 }
15946
15947 void
15948 BackwardEvent ()
15949 {
15950     if (gameMode == IcsExamining && !pausing) {
15951         SendToICS(ics_prefix);
15952         SendToICS("backward\n");
15953     } else {
15954         BackwardInner(currentMove - 1);
15955     }
15956 }
15957
15958 void
15959 ToStartEvent ()
15960 {
15961     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15962         /* to optimize, we temporarily turn off analysis mode while we undo
15963          * all the moves. Otherwise we get analysis output after each undo.
15964          */
15965         if (first.analysisSupport) {
15966           SendToProgram("exit\nforce\n", &first);
15967           first.analyzing = FALSE;
15968         }
15969     }
15970
15971     if (gameMode == IcsExamining && !pausing) {
15972         SendToICS(ics_prefix);
15973         SendToICS("backward 999999\n");
15974     } else {
15975         BackwardInner(backwardMostMove);
15976     }
15977
15978     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15979         /* we have fed all the moves, so reactivate analysis mode */
15980         SendToProgram("analyze\n", &first);
15981         first.analyzing = TRUE;
15982         /*first.maybeThinking = TRUE;*/
15983         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15984     }
15985 }
15986
15987 void
15988 ToNrEvent (int to)
15989 {
15990   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15991   if (to >= forwardMostMove) to = forwardMostMove;
15992   if (to <= backwardMostMove) to = backwardMostMove;
15993   if (to < currentMove) {
15994     BackwardInner(to);
15995   } else {
15996     ForwardInner(to);
15997   }
15998 }
15999
16000 void
16001 RevertEvent (Boolean annotate)
16002 {
16003     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16004         return;
16005     }
16006     if (gameMode != IcsExamining) {
16007         DisplayError(_("You are not examining a game"), 0);
16008         return;
16009     }
16010     if (pausing) {
16011         DisplayError(_("You can't revert while pausing"), 0);
16012         return;
16013     }
16014     SendToICS(ics_prefix);
16015     SendToICS("revert\n");
16016 }
16017
16018 void
16019 RetractMoveEvent ()
16020 {
16021     switch (gameMode) {
16022       case MachinePlaysWhite:
16023       case MachinePlaysBlack:
16024         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16025             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16026             return;
16027         }
16028         if (forwardMostMove < 2) return;
16029         currentMove = forwardMostMove = forwardMostMove - 2;
16030         whiteTimeRemaining = timeRemaining[0][currentMove];
16031         blackTimeRemaining = timeRemaining[1][currentMove];
16032         DisplayBothClocks();
16033         DisplayMove(currentMove - 1);
16034         ClearHighlights();/*!! could figure this out*/
16035         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16036         SendToProgram("remove\n", &first);
16037         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16038         break;
16039
16040       case BeginningOfGame:
16041       default:
16042         break;
16043
16044       case IcsPlayingWhite:
16045       case IcsPlayingBlack:
16046         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16047             SendToICS(ics_prefix);
16048             SendToICS("takeback 2\n");
16049         } else {
16050             SendToICS(ics_prefix);
16051             SendToICS("takeback 1\n");
16052         }
16053         break;
16054     }
16055 }
16056
16057 void
16058 MoveNowEvent ()
16059 {
16060     ChessProgramState *cps;
16061
16062     switch (gameMode) {
16063       case MachinePlaysWhite:
16064         if (!WhiteOnMove(forwardMostMove)) {
16065             DisplayError(_("It is your turn"), 0);
16066             return;
16067         }
16068         cps = &first;
16069         break;
16070       case MachinePlaysBlack:
16071         if (WhiteOnMove(forwardMostMove)) {
16072             DisplayError(_("It is your turn"), 0);
16073             return;
16074         }
16075         cps = &first;
16076         break;
16077       case TwoMachinesPlay:
16078         if (WhiteOnMove(forwardMostMove) ==
16079             (first.twoMachinesColor[0] == 'w')) {
16080             cps = &first;
16081         } else {
16082             cps = &second;
16083         }
16084         break;
16085       case BeginningOfGame:
16086       default:
16087         return;
16088     }
16089     SendToProgram("?\n", cps);
16090 }
16091
16092 void
16093 TruncateGameEvent ()
16094 {
16095     EditGameEvent();
16096     if (gameMode != EditGame) return;
16097     TruncateGame();
16098 }
16099
16100 void
16101 TruncateGame ()
16102 {
16103     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16104     if (forwardMostMove > currentMove) {
16105         if (gameInfo.resultDetails != NULL) {
16106             free(gameInfo.resultDetails);
16107             gameInfo.resultDetails = NULL;
16108             gameInfo.result = GameUnfinished;
16109         }
16110         forwardMostMove = currentMove;
16111         HistorySet(parseList, backwardMostMove, forwardMostMove,
16112                    currentMove-1);
16113     }
16114 }
16115
16116 void
16117 HintEvent ()
16118 {
16119     if (appData.noChessProgram) return;
16120     switch (gameMode) {
16121       case MachinePlaysWhite:
16122         if (WhiteOnMove(forwardMostMove)) {
16123             DisplayError(_("Wait until your turn."), 0);
16124             return;
16125         }
16126         break;
16127       case BeginningOfGame:
16128       case MachinePlaysBlack:
16129         if (!WhiteOnMove(forwardMostMove)) {
16130             DisplayError(_("Wait until your turn."), 0);
16131             return;
16132         }
16133         break;
16134       default:
16135         DisplayError(_("No hint available"), 0);
16136         return;
16137     }
16138     SendToProgram("hint\n", &first);
16139     hintRequested = TRUE;
16140 }
16141
16142 int
16143 SaveSelected (FILE *g, int dummy, char *dummy2)
16144 {
16145     ListGame * lg = (ListGame *) gameList.head;
16146     int nItem, cnt=0;
16147     FILE *f;
16148
16149     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16150         DisplayError(_("Game list not loaded or empty"), 0);
16151         return 0;
16152     }
16153
16154     creatingBook = TRUE; // suppresses stuff during load game
16155
16156     /* Get list size */
16157     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16158         if(lg->position >= 0) { // selected?
16159             LoadGame(f, nItem, "", TRUE);
16160             SaveGamePGN2(g); // leaves g open
16161             cnt++; DoEvents();
16162         }
16163         lg = (ListGame *) lg->node.succ;
16164     }
16165
16166     fclose(g);
16167     creatingBook = FALSE;
16168
16169     return cnt;
16170 }
16171
16172 void
16173 CreateBookEvent ()
16174 {
16175     ListGame * lg = (ListGame *) gameList.head;
16176     FILE *f, *g;
16177     int nItem;
16178     static int secondTime = FALSE;
16179
16180     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16181         DisplayError(_("Game list not loaded or empty"), 0);
16182         return;
16183     }
16184
16185     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16186         fclose(g);
16187         secondTime++;
16188         DisplayNote(_("Book file exists! Try again for overwrite."));
16189         return;
16190     }
16191
16192     creatingBook = TRUE;
16193     secondTime = FALSE;
16194
16195     /* Get list size */
16196     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16197         if(lg->position >= 0) {
16198             LoadGame(f, nItem, "", TRUE);
16199             AddGameToBook(TRUE);
16200             DoEvents();
16201         }
16202         lg = (ListGame *) lg->node.succ;
16203     }
16204
16205     creatingBook = FALSE;
16206     FlushBook();
16207 }
16208
16209 void
16210 BookEvent ()
16211 {
16212     if (appData.noChessProgram) return;
16213     switch (gameMode) {
16214       case MachinePlaysWhite:
16215         if (WhiteOnMove(forwardMostMove)) {
16216             DisplayError(_("Wait until your turn."), 0);
16217             return;
16218         }
16219         break;
16220       case BeginningOfGame:
16221       case MachinePlaysBlack:
16222         if (!WhiteOnMove(forwardMostMove)) {
16223             DisplayError(_("Wait until your turn."), 0);
16224             return;
16225         }
16226         break;
16227       case EditPosition:
16228         EditPositionDone(TRUE);
16229         break;
16230       case TwoMachinesPlay:
16231         return;
16232       default:
16233         break;
16234     }
16235     SendToProgram("bk\n", &first);
16236     bookOutput[0] = NULLCHAR;
16237     bookRequested = TRUE;
16238 }
16239
16240 void
16241 AboutGameEvent ()
16242 {
16243     char *tags = PGNTags(&gameInfo);
16244     TagsPopUp(tags, CmailMsg());
16245     free(tags);
16246 }
16247
16248 /* end button procedures */
16249
16250 void
16251 PrintPosition (FILE *fp, int move)
16252 {
16253     int i, j;
16254
16255     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16256         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16257             char c = PieceToChar(boards[move][i][j]);
16258             fputc(c == 'x' ? '.' : c, fp);
16259             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16260         }
16261     }
16262     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16263       fprintf(fp, "white to play\n");
16264     else
16265       fprintf(fp, "black to play\n");
16266 }
16267
16268 void
16269 PrintOpponents (FILE *fp)
16270 {
16271     if (gameInfo.white != NULL) {
16272         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16273     } else {
16274         fprintf(fp, "\n");
16275     }
16276 }
16277
16278 /* Find last component of program's own name, using some heuristics */
16279 void
16280 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16281 {
16282     char *p, *q, c;
16283     int local = (strcmp(host, "localhost") == 0);
16284     while (!local && (p = strchr(prog, ';')) != NULL) {
16285         p++;
16286         while (*p == ' ') p++;
16287         prog = p;
16288     }
16289     if (*prog == '"' || *prog == '\'') {
16290         q = strchr(prog + 1, *prog);
16291     } else {
16292         q = strchr(prog, ' ');
16293     }
16294     if (q == NULL) q = prog + strlen(prog);
16295     p = q;
16296     while (p >= prog && *p != '/' && *p != '\\') p--;
16297     p++;
16298     if(p == prog && *p == '"') p++;
16299     c = *q; *q = 0;
16300     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16301     memcpy(buf, p, q - p);
16302     buf[q - p] = NULLCHAR;
16303     if (!local) {
16304         strcat(buf, "@");
16305         strcat(buf, host);
16306     }
16307 }
16308
16309 char *
16310 TimeControlTagValue ()
16311 {
16312     char buf[MSG_SIZ];
16313     if (!appData.clockMode) {
16314       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16315     } else if (movesPerSession > 0) {
16316       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16317     } else if (timeIncrement == 0) {
16318       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16319     } else {
16320       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16321     }
16322     return StrSave(buf);
16323 }
16324
16325 void
16326 SetGameInfo ()
16327 {
16328     /* This routine is used only for certain modes */
16329     VariantClass v = gameInfo.variant;
16330     ChessMove r = GameUnfinished;
16331     char *p = NULL;
16332
16333     if(keepInfo) return;
16334
16335     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16336         r = gameInfo.result;
16337         p = gameInfo.resultDetails;
16338         gameInfo.resultDetails = NULL;
16339     }
16340     ClearGameInfo(&gameInfo);
16341     gameInfo.variant = v;
16342
16343     switch (gameMode) {
16344       case MachinePlaysWhite:
16345         gameInfo.event = StrSave( appData.pgnEventHeader );
16346         gameInfo.site = StrSave(HostName());
16347         gameInfo.date = PGNDate();
16348         gameInfo.round = StrSave("-");
16349         gameInfo.white = StrSave(first.tidy);
16350         gameInfo.black = StrSave(UserName());
16351         gameInfo.timeControl = TimeControlTagValue();
16352         break;
16353
16354       case MachinePlaysBlack:
16355         gameInfo.event = StrSave( appData.pgnEventHeader );
16356         gameInfo.site = StrSave(HostName());
16357         gameInfo.date = PGNDate();
16358         gameInfo.round = StrSave("-");
16359         gameInfo.white = StrSave(UserName());
16360         gameInfo.black = StrSave(first.tidy);
16361         gameInfo.timeControl = TimeControlTagValue();
16362         break;
16363
16364       case TwoMachinesPlay:
16365         gameInfo.event = StrSave( appData.pgnEventHeader );
16366         gameInfo.site = StrSave(HostName());
16367         gameInfo.date = PGNDate();
16368         if (roundNr > 0) {
16369             char buf[MSG_SIZ];
16370             snprintf(buf, MSG_SIZ, "%d", roundNr);
16371             gameInfo.round = StrSave(buf);
16372         } else {
16373             gameInfo.round = StrSave("-");
16374         }
16375         if (first.twoMachinesColor[0] == 'w') {
16376             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16377             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16378         } else {
16379             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16380             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16381         }
16382         gameInfo.timeControl = TimeControlTagValue();
16383         break;
16384
16385       case EditGame:
16386         gameInfo.event = StrSave("Edited game");
16387         gameInfo.site = StrSave(HostName());
16388         gameInfo.date = PGNDate();
16389         gameInfo.round = StrSave("-");
16390         gameInfo.white = StrSave("-");
16391         gameInfo.black = StrSave("-");
16392         gameInfo.result = r;
16393         gameInfo.resultDetails = p;
16394         break;
16395
16396       case EditPosition:
16397         gameInfo.event = StrSave("Edited position");
16398         gameInfo.site = StrSave(HostName());
16399         gameInfo.date = PGNDate();
16400         gameInfo.round = StrSave("-");
16401         gameInfo.white = StrSave("-");
16402         gameInfo.black = StrSave("-");
16403         break;
16404
16405       case IcsPlayingWhite:
16406       case IcsPlayingBlack:
16407       case IcsObserving:
16408       case IcsExamining:
16409         break;
16410
16411       case PlayFromGameFile:
16412         gameInfo.event = StrSave("Game from non-PGN file");
16413         gameInfo.site = StrSave(HostName());
16414         gameInfo.date = PGNDate();
16415         gameInfo.round = StrSave("-");
16416         gameInfo.white = StrSave("?");
16417         gameInfo.black = StrSave("?");
16418         break;
16419
16420       default:
16421         break;
16422     }
16423 }
16424
16425 void
16426 ReplaceComment (int index, char *text)
16427 {
16428     int len;
16429     char *p;
16430     float score;
16431
16432     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16433        pvInfoList[index-1].depth == len &&
16434        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16435        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16436     while (*text == '\n') text++;
16437     len = strlen(text);
16438     while (len > 0 && text[len - 1] == '\n') len--;
16439
16440     if (commentList[index] != NULL)
16441       free(commentList[index]);
16442
16443     if (len == 0) {
16444         commentList[index] = NULL;
16445         return;
16446     }
16447   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16448       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16449       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16450     commentList[index] = (char *) malloc(len + 2);
16451     strncpy(commentList[index], text, len);
16452     commentList[index][len] = '\n';
16453     commentList[index][len + 1] = NULLCHAR;
16454   } else {
16455     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16456     char *p;
16457     commentList[index] = (char *) malloc(len + 7);
16458     safeStrCpy(commentList[index], "{\n", 3);
16459     safeStrCpy(commentList[index]+2, text, len+1);
16460     commentList[index][len+2] = NULLCHAR;
16461     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16462     strcat(commentList[index], "\n}\n");
16463   }
16464 }
16465
16466 void
16467 CrushCRs (char *text)
16468 {
16469   char *p = text;
16470   char *q = text;
16471   char ch;
16472
16473   do {
16474     ch = *p++;
16475     if (ch == '\r') continue;
16476     *q++ = ch;
16477   } while (ch != '\0');
16478 }
16479
16480 void
16481 AppendComment (int index, char *text, Boolean addBraces)
16482 /* addBraces  tells if we should add {} */
16483 {
16484     int oldlen, len;
16485     char *old;
16486
16487 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16488     if(addBraces == 3) addBraces = 0; else // force appending literally
16489     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16490
16491     CrushCRs(text);
16492     while (*text == '\n') text++;
16493     len = strlen(text);
16494     while (len > 0 && text[len - 1] == '\n') len--;
16495     text[len] = NULLCHAR;
16496
16497     if (len == 0) return;
16498
16499     if (commentList[index] != NULL) {
16500       Boolean addClosingBrace = addBraces;
16501         old = commentList[index];
16502         oldlen = strlen(old);
16503         while(commentList[index][oldlen-1] ==  '\n')
16504           commentList[index][--oldlen] = NULLCHAR;
16505         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16506         safeStrCpy(commentList[index], old, oldlen + len + 6);
16507         free(old);
16508         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16509         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16510           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16511           while (*text == '\n') { text++; len--; }
16512           commentList[index][--oldlen] = NULLCHAR;
16513       }
16514         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16515         else          strcat(commentList[index], "\n");
16516         strcat(commentList[index], text);
16517         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16518         else          strcat(commentList[index], "\n");
16519     } else {
16520         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16521         if(addBraces)
16522           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16523         else commentList[index][0] = NULLCHAR;
16524         strcat(commentList[index], text);
16525         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16526         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16527     }
16528 }
16529
16530 static char *
16531 FindStr (char * text, char * sub_text)
16532 {
16533     char * result = strstr( text, sub_text );
16534
16535     if( result != NULL ) {
16536         result += strlen( sub_text );
16537     }
16538
16539     return result;
16540 }
16541
16542 /* [AS] Try to extract PV info from PGN comment */
16543 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16544 char *
16545 GetInfoFromComment (int index, char * text)
16546 {
16547     char * sep = text, *p;
16548
16549     if( text != NULL && index > 0 ) {
16550         int score = 0;
16551         int depth = 0;
16552         int time = -1, sec = 0, deci;
16553         char * s_eval = FindStr( text, "[%eval " );
16554         char * s_emt = FindStr( text, "[%emt " );
16555 #if 0
16556         if( s_eval != NULL || s_emt != NULL ) {
16557 #else
16558         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16559 #endif
16560             /* New style */
16561             char delim;
16562
16563             if( s_eval != NULL ) {
16564                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16565                     return text;
16566                 }
16567
16568                 if( delim != ']' ) {
16569                     return text;
16570                 }
16571             }
16572
16573             if( s_emt != NULL ) {
16574             }
16575                 return text;
16576         }
16577         else {
16578             /* We expect something like: [+|-]nnn.nn/dd */
16579             int score_lo = 0;
16580
16581             if(*text != '{') return text; // [HGM] braces: must be normal comment
16582
16583             sep = strchr( text, '/' );
16584             if( sep == NULL || sep < (text+4) ) {
16585                 return text;
16586             }
16587
16588             p = text;
16589             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16590             if(p[1] == '(') { // comment starts with PV
16591                p = strchr(p, ')'); // locate end of PV
16592                if(p == NULL || sep < p+5) return text;
16593                // at this point we have something like "{(.*) +0.23/6 ..."
16594                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16595                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16596                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16597             }
16598             time = -1; sec = -1; deci = -1;
16599             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16600                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16601                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16602                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16603                 return text;
16604             }
16605
16606             if( score_lo < 0 || score_lo >= 100 ) {
16607                 return text;
16608             }
16609
16610             if(sec >= 0) time = 600*time + 10*sec; else
16611             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16612
16613             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16614
16615             /* [HGM] PV time: now locate end of PV info */
16616             while( *++sep >= '0' && *sep <= '9'); // strip depth
16617             if(time >= 0)
16618             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16619             if(sec >= 0)
16620             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16621             if(deci >= 0)
16622             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16623             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16624         }
16625
16626         if( depth <= 0 ) {
16627             return text;
16628         }
16629
16630         if( time < 0 ) {
16631             time = -1;
16632         }
16633
16634         pvInfoList[index-1].depth = depth;
16635         pvInfoList[index-1].score = score;
16636         pvInfoList[index-1].time  = 10*time; // centi-sec
16637         if(*sep == '}') *sep = 0; else *--sep = '{';
16638         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16639     }
16640     return sep;
16641 }
16642
16643 void
16644 SendToProgram (char *message, ChessProgramState *cps)
16645 {
16646     int count, outCount, error;
16647     char buf[MSG_SIZ];
16648
16649     if (cps->pr == NoProc) return;
16650     Attention(cps);
16651
16652     if (appData.debugMode) {
16653         TimeMark now;
16654         GetTimeMark(&now);
16655         fprintf(debugFP, "%ld >%-6s: %s",
16656                 SubtractTimeMarks(&now, &programStartTime),
16657                 cps->which, message);
16658         if(serverFP)
16659             fprintf(serverFP, "%ld >%-6s: %s",
16660                 SubtractTimeMarks(&now, &programStartTime),
16661                 cps->which, message), fflush(serverFP);
16662     }
16663
16664     count = strlen(message);
16665     outCount = OutputToProcess(cps->pr, message, count, &error);
16666     if (outCount < count && !exiting
16667                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16668       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16669       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16670         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16671             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16672                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16673                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16674                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16675             } else {
16676                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16677                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16678                 gameInfo.result = res;
16679             }
16680             gameInfo.resultDetails = StrSave(buf);
16681         }
16682         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16683         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16684     }
16685 }
16686
16687 void
16688 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16689 {
16690     char *end_str;
16691     char buf[MSG_SIZ];
16692     ChessProgramState *cps = (ChessProgramState *)closure;
16693
16694     if (isr != cps->isr) return; /* Killed intentionally */
16695     if (count <= 0) {
16696         if (count == 0) {
16697             RemoveInputSource(cps->isr);
16698             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16699                     _(cps->which), cps->program);
16700             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16701             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16702                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16703                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16704                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16705                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16706                 } else {
16707                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16708                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16709                     gameInfo.result = res;
16710                 }
16711                 gameInfo.resultDetails = StrSave(buf);
16712             }
16713             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16714             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16715         } else {
16716             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16717                     _(cps->which), cps->program);
16718             RemoveInputSource(cps->isr);
16719
16720             /* [AS] Program is misbehaving badly... kill it */
16721             if( count == -2 ) {
16722                 DestroyChildProcess( cps->pr, 9 );
16723                 cps->pr = NoProc;
16724             }
16725
16726             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16727         }
16728         return;
16729     }
16730
16731     if ((end_str = strchr(message, '\r')) != NULL)
16732       *end_str = NULLCHAR;
16733     if ((end_str = strchr(message, '\n')) != NULL)
16734       *end_str = NULLCHAR;
16735
16736     if (appData.debugMode) {
16737         TimeMark now; int print = 1;
16738         char *quote = ""; char c; int i;
16739
16740         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16741                 char start = message[0];
16742                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16743                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16744                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16745                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16746                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16747                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16748                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16749                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16750                    sscanf(message, "hint: %c", &c)!=1 &&
16751                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16752                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16753                     print = (appData.engineComments >= 2);
16754                 }
16755                 message[0] = start; // restore original message
16756         }
16757         if(print) {
16758                 GetTimeMark(&now);
16759                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16760                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16761                         quote,
16762                         message);
16763                 if(serverFP)
16764                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16765                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16766                         quote,
16767                         message), fflush(serverFP);
16768         }
16769     }
16770
16771     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16772     if (appData.icsEngineAnalyze) {
16773         if (strstr(message, "whisper") != NULL ||
16774              strstr(message, "kibitz") != NULL ||
16775             strstr(message, "tellics") != NULL) return;
16776     }
16777
16778     HandleMachineMove(message, cps);
16779 }
16780
16781
16782 void
16783 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16784 {
16785     char buf[MSG_SIZ];
16786     int seconds;
16787
16788     if( timeControl_2 > 0 ) {
16789         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16790             tc = timeControl_2;
16791         }
16792     }
16793     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16794     inc /= cps->timeOdds;
16795     st  /= cps->timeOdds;
16796
16797     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16798
16799     if (st > 0) {
16800       /* Set exact time per move, normally using st command */
16801       if (cps->stKludge) {
16802         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16803         seconds = st % 60;
16804         if (seconds == 0) {
16805           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16806         } else {
16807           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16808         }
16809       } else {
16810         snprintf(buf, MSG_SIZ, "st %d\n", st);
16811       }
16812     } else {
16813       /* Set conventional or incremental time control, using level command */
16814       if (seconds == 0) {
16815         /* Note old gnuchess bug -- minutes:seconds used to not work.
16816            Fixed in later versions, but still avoid :seconds
16817            when seconds is 0. */
16818         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16819       } else {
16820         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16821                  seconds, inc/1000.);
16822       }
16823     }
16824     SendToProgram(buf, cps);
16825
16826     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16827     /* Orthogonally, limit search to given depth */
16828     if (sd > 0) {
16829       if (cps->sdKludge) {
16830         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16831       } else {
16832         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16833       }
16834       SendToProgram(buf, cps);
16835     }
16836
16837     if(cps->nps >= 0) { /* [HGM] nps */
16838         if(cps->supportsNPS == FALSE)
16839           cps->nps = -1; // don't use if engine explicitly says not supported!
16840         else {
16841           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16842           SendToProgram(buf, cps);
16843         }
16844     }
16845 }
16846
16847 ChessProgramState *
16848 WhitePlayer ()
16849 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16850 {
16851     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16852        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16853         return &second;
16854     return &first;
16855 }
16856
16857 void
16858 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16859 {
16860     char message[MSG_SIZ];
16861     long time, otime;
16862
16863     /* Note: this routine must be called when the clocks are stopped
16864        or when they have *just* been set or switched; otherwise
16865        it will be off by the time since the current tick started.
16866     */
16867     if (machineWhite) {
16868         time = whiteTimeRemaining / 10;
16869         otime = blackTimeRemaining / 10;
16870     } else {
16871         time = blackTimeRemaining / 10;
16872         otime = whiteTimeRemaining / 10;
16873     }
16874     /* [HGM] translate opponent's time by time-odds factor */
16875     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16876
16877     if (time <= 0) time = 1;
16878     if (otime <= 0) otime = 1;
16879
16880     snprintf(message, MSG_SIZ, "time %ld\n", time);
16881     SendToProgram(message, cps);
16882
16883     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16884     SendToProgram(message, cps);
16885 }
16886
16887 char *
16888 EngineDefinedVariant (ChessProgramState *cps, int n)
16889 {   // return name of n-th unknown variant that engine supports
16890     static char buf[MSG_SIZ];
16891     char *p, *s = cps->variants;
16892     if(!s) return NULL;
16893     do { // parse string from variants feature
16894       VariantClass v;
16895         p = strchr(s, ',');
16896         if(p) *p = NULLCHAR;
16897       v = StringToVariant(s);
16898       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16899         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16900             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16901                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16902                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16903                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16904             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16905         }
16906         if(p) *p++ = ',';
16907         if(n < 0) return buf;
16908     } while(s = p);
16909     return NULL;
16910 }
16911
16912 int
16913 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16914 {
16915   char buf[MSG_SIZ];
16916   int len = strlen(name);
16917   int val;
16918
16919   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16920     (*p) += len + 1;
16921     sscanf(*p, "%d", &val);
16922     *loc = (val != 0);
16923     while (**p && **p != ' ')
16924       (*p)++;
16925     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16926     SendToProgram(buf, cps);
16927     return TRUE;
16928   }
16929   return FALSE;
16930 }
16931
16932 int
16933 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16934 {
16935   char buf[MSG_SIZ];
16936   int len = strlen(name);
16937   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16938     (*p) += len + 1;
16939     sscanf(*p, "%d", loc);
16940     while (**p && **p != ' ') (*p)++;
16941     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16942     SendToProgram(buf, cps);
16943     return TRUE;
16944   }
16945   return FALSE;
16946 }
16947
16948 int
16949 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16950 {
16951   char buf[MSG_SIZ];
16952   int len = strlen(name);
16953   if (strncmp((*p), name, len) == 0
16954       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16955     (*p) += len + 2;
16956     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16957     sscanf(*p, "%[^\"]", *loc);
16958     while (**p && **p != '\"') (*p)++;
16959     if (**p == '\"') (*p)++;
16960     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16961     SendToProgram(buf, cps);
16962     return TRUE;
16963   }
16964   return FALSE;
16965 }
16966
16967 int
16968 ParseOption (Option *opt, ChessProgramState *cps)
16969 // [HGM] options: process the string that defines an engine option, and determine
16970 // name, type, default value, and allowed value range
16971 {
16972         char *p, *q, buf[MSG_SIZ];
16973         int n, min = (-1)<<31, max = 1<<31, def;
16974
16975         if(p = strstr(opt->name, " -spin ")) {
16976             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16977             if(max < min) max = min; // enforce consistency
16978             if(def < min) def = min;
16979             if(def > max) def = max;
16980             opt->value = def;
16981             opt->min = min;
16982             opt->max = max;
16983             opt->type = Spin;
16984         } else if((p = strstr(opt->name, " -slider "))) {
16985             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16986             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16987             if(max < min) max = min; // enforce consistency
16988             if(def < min) def = min;
16989             if(def > max) def = max;
16990             opt->value = def;
16991             opt->min = min;
16992             opt->max = max;
16993             opt->type = Spin; // Slider;
16994         } else if((p = strstr(opt->name, " -string "))) {
16995             opt->textValue = p+9;
16996             opt->type = TextBox;
16997         } else if((p = strstr(opt->name, " -file "))) {
16998             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16999             opt->textValue = p+7;
17000             opt->type = FileName; // FileName;
17001         } else if((p = strstr(opt->name, " -path "))) {
17002             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17003             opt->textValue = p+7;
17004             opt->type = PathName; // PathName;
17005         } else if(p = strstr(opt->name, " -check ")) {
17006             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17007             opt->value = (def != 0);
17008             opt->type = CheckBox;
17009         } else if(p = strstr(opt->name, " -combo ")) {
17010             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17011             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17012             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17013             opt->value = n = 0;
17014             while(q = StrStr(q, " /// ")) {
17015                 n++; *q = 0;    // count choices, and null-terminate each of them
17016                 q += 5;
17017                 if(*q == '*') { // remember default, which is marked with * prefix
17018                     q++;
17019                     opt->value = n;
17020                 }
17021                 cps->comboList[cps->comboCnt++] = q;
17022             }
17023             cps->comboList[cps->comboCnt++] = NULL;
17024             opt->max = n + 1;
17025             opt->type = ComboBox;
17026         } else if(p = strstr(opt->name, " -button")) {
17027             opt->type = Button;
17028         } else if(p = strstr(opt->name, " -save")) {
17029             opt->type = SaveButton;
17030         } else return FALSE;
17031         *p = 0; // terminate option name
17032         // now look if the command-line options define a setting for this engine option.
17033         if(cps->optionSettings && cps->optionSettings[0])
17034             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17035         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17036           snprintf(buf, MSG_SIZ, "option %s", p);
17037                 if(p = strstr(buf, ",")) *p = 0;
17038                 if(q = strchr(buf, '=')) switch(opt->type) {
17039                     case ComboBox:
17040                         for(n=0; n<opt->max; n++)
17041                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17042                         break;
17043                     case TextBox:
17044                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17045                         break;
17046                     case Spin:
17047                     case CheckBox:
17048                         opt->value = atoi(q+1);
17049                     default:
17050                         break;
17051                 }
17052                 strcat(buf, "\n");
17053                 SendToProgram(buf, cps);
17054         }
17055         return TRUE;
17056 }
17057
17058 void
17059 FeatureDone (ChessProgramState *cps, int val)
17060 {
17061   DelayedEventCallback cb = GetDelayedEvent();
17062   if ((cb == InitBackEnd3 && cps == &first) ||
17063       (cb == SettingsMenuIfReady && cps == &second) ||
17064       (cb == LoadEngine) ||
17065       (cb == TwoMachinesEventIfReady)) {
17066     CancelDelayedEvent();
17067     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17068   }
17069   cps->initDone = val;
17070   if(val) cps->reload = FALSE;
17071 }
17072
17073 /* Parse feature command from engine */
17074 void
17075 ParseFeatures (char *args, ChessProgramState *cps)
17076 {
17077   char *p = args;
17078   char *q = NULL;
17079   int val;
17080   char buf[MSG_SIZ];
17081
17082   for (;;) {
17083     while (*p == ' ') p++;
17084     if (*p == NULLCHAR) return;
17085
17086     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17087     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17088     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17089     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17090     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17091     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17092     if (BoolFeature(&p, "reuse", &val, cps)) {
17093       /* Engine can disable reuse, but can't enable it if user said no */
17094       if (!val) cps->reuse = FALSE;
17095       continue;
17096     }
17097     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17098     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17099       if (gameMode == TwoMachinesPlay) {
17100         DisplayTwoMachinesTitle();
17101       } else {
17102         DisplayTitle("");
17103       }
17104       continue;
17105     }
17106     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17107     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17108     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17109     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17110     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17111     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17112     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17113     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17114     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17115     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17116     if (IntFeature(&p, "done", &val, cps)) {
17117       FeatureDone(cps, val);
17118       continue;
17119     }
17120     /* Added by Tord: */
17121     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17122     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17123     /* End of additions by Tord */
17124
17125     /* [HGM] added features: */
17126     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17127     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17128     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17129     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17130     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17131     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17132     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17133     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17134         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17135         FREE(cps->option[cps->nrOptions].name);
17136         cps->option[cps->nrOptions].name = q; q = NULL;
17137         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17138           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17139             SendToProgram(buf, cps);
17140             continue;
17141         }
17142         if(cps->nrOptions >= MAX_OPTIONS) {
17143             cps->nrOptions--;
17144             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17145             DisplayError(buf, 0);
17146         }
17147         continue;
17148     }
17149     /* End of additions by HGM */
17150
17151     /* unknown feature: complain and skip */
17152     q = p;
17153     while (*q && *q != '=') q++;
17154     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17155     SendToProgram(buf, cps);
17156     p = q;
17157     if (*p == '=') {
17158       p++;
17159       if (*p == '\"') {
17160         p++;
17161         while (*p && *p != '\"') p++;
17162         if (*p == '\"') p++;
17163       } else {
17164         while (*p && *p != ' ') p++;
17165       }
17166     }
17167   }
17168
17169 }
17170
17171 void
17172 PeriodicUpdatesEvent (int newState)
17173 {
17174     if (newState == appData.periodicUpdates)
17175       return;
17176
17177     appData.periodicUpdates=newState;
17178
17179     /* Display type changes, so update it now */
17180 //    DisplayAnalysis();
17181
17182     /* Get the ball rolling again... */
17183     if (newState) {
17184         AnalysisPeriodicEvent(1);
17185         StartAnalysisClock();
17186     }
17187 }
17188
17189 void
17190 PonderNextMoveEvent (int newState)
17191 {
17192     if (newState == appData.ponderNextMove) return;
17193     if (gameMode == EditPosition) EditPositionDone(TRUE);
17194     if (newState) {
17195         SendToProgram("hard\n", &first);
17196         if (gameMode == TwoMachinesPlay) {
17197             SendToProgram("hard\n", &second);
17198         }
17199     } else {
17200         SendToProgram("easy\n", &first);
17201         thinkOutput[0] = NULLCHAR;
17202         if (gameMode == TwoMachinesPlay) {
17203             SendToProgram("easy\n", &second);
17204         }
17205     }
17206     appData.ponderNextMove = newState;
17207 }
17208
17209 void
17210 NewSettingEvent (int option, int *feature, char *command, int value)
17211 {
17212     char buf[MSG_SIZ];
17213
17214     if (gameMode == EditPosition) EditPositionDone(TRUE);
17215     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17216     if(feature == NULL || *feature) SendToProgram(buf, &first);
17217     if (gameMode == TwoMachinesPlay) {
17218         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17219     }
17220 }
17221
17222 void
17223 ShowThinkingEvent ()
17224 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17225 {
17226     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17227     int newState = appData.showThinking
17228         // [HGM] thinking: other features now need thinking output as well
17229         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17230
17231     if (oldState == newState) return;
17232     oldState = newState;
17233     if (gameMode == EditPosition) EditPositionDone(TRUE);
17234     if (oldState) {
17235         SendToProgram("post\n", &first);
17236         if (gameMode == TwoMachinesPlay) {
17237             SendToProgram("post\n", &second);
17238         }
17239     } else {
17240         SendToProgram("nopost\n", &first);
17241         thinkOutput[0] = NULLCHAR;
17242         if (gameMode == TwoMachinesPlay) {
17243             SendToProgram("nopost\n", &second);
17244         }
17245     }
17246 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17247 }
17248
17249 void
17250 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17251 {
17252   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17253   if (pr == NoProc) return;
17254   AskQuestion(title, question, replyPrefix, pr);
17255 }
17256
17257 void
17258 TypeInEvent (char firstChar)
17259 {
17260     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17261         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17262         gameMode == AnalyzeMode || gameMode == EditGame ||
17263         gameMode == EditPosition || gameMode == IcsExamining ||
17264         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17265         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17266                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17267                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17268         gameMode == Training) PopUpMoveDialog(firstChar);
17269 }
17270
17271 void
17272 TypeInDoneEvent (char *move)
17273 {
17274         Board board;
17275         int n, fromX, fromY, toX, toY;
17276         char promoChar;
17277         ChessMove moveType;
17278
17279         // [HGM] FENedit
17280         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17281                 EditPositionPasteFEN(move);
17282                 return;
17283         }
17284         // [HGM] movenum: allow move number to be typed in any mode
17285         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17286           ToNrEvent(2*n-1);
17287           return;
17288         }
17289         // undocumented kludge: allow command-line option to be typed in!
17290         // (potentially fatal, and does not implement the effect of the option.)
17291         // should only be used for options that are values on which future decisions will be made,
17292         // and definitely not on options that would be used during initialization.
17293         if(strstr(move, "!!! -") == move) {
17294             ParseArgsFromString(move+4);
17295             return;
17296         }
17297
17298       if (gameMode != EditGame && currentMove != forwardMostMove &&
17299         gameMode != Training) {
17300         DisplayMoveError(_("Displayed move is not current"));
17301       } else {
17302         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17303           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17304         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17305         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17306           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17307           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17308         } else {
17309           DisplayMoveError(_("Could not parse move"));
17310         }
17311       }
17312 }
17313
17314 void
17315 DisplayMove (int moveNumber)
17316 {
17317     char message[MSG_SIZ];
17318     char res[MSG_SIZ];
17319     char cpThinkOutput[MSG_SIZ];
17320
17321     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17322
17323     if (moveNumber == forwardMostMove - 1 ||
17324         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17325
17326         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17327
17328         if (strchr(cpThinkOutput, '\n')) {
17329             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17330         }
17331     } else {
17332         *cpThinkOutput = NULLCHAR;
17333     }
17334
17335     /* [AS] Hide thinking from human user */
17336     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17337         *cpThinkOutput = NULLCHAR;
17338         if( thinkOutput[0] != NULLCHAR ) {
17339             int i;
17340
17341             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17342                 cpThinkOutput[i] = '.';
17343             }
17344             cpThinkOutput[i] = NULLCHAR;
17345             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17346         }
17347     }
17348
17349     if (moveNumber == forwardMostMove - 1 &&
17350         gameInfo.resultDetails != NULL) {
17351         if (gameInfo.resultDetails[0] == NULLCHAR) {
17352           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17353         } else {
17354           snprintf(res, MSG_SIZ, " {%s} %s",
17355                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17356         }
17357     } else {
17358         res[0] = NULLCHAR;
17359     }
17360
17361     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17362         DisplayMessage(res, cpThinkOutput);
17363     } else {
17364       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17365                 WhiteOnMove(moveNumber) ? " " : ".. ",
17366                 parseList[moveNumber], res);
17367         DisplayMessage(message, cpThinkOutput);
17368     }
17369 }
17370
17371 void
17372 DisplayComment (int moveNumber, char *text)
17373 {
17374     char title[MSG_SIZ];
17375
17376     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17377       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17378     } else {
17379       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17380               WhiteOnMove(moveNumber) ? " " : ".. ",
17381               parseList[moveNumber]);
17382     }
17383     if (text != NULL && (appData.autoDisplayComment || commentUp))
17384         CommentPopUp(title, text);
17385 }
17386
17387 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17388  * might be busy thinking or pondering.  It can be omitted if your
17389  * gnuchess is configured to stop thinking immediately on any user
17390  * input.  However, that gnuchess feature depends on the FIONREAD
17391  * ioctl, which does not work properly on some flavors of Unix.
17392  */
17393 void
17394 Attention (ChessProgramState *cps)
17395 {
17396 #if ATTENTION
17397     if (!cps->useSigint) return;
17398     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17399     switch (gameMode) {
17400       case MachinePlaysWhite:
17401       case MachinePlaysBlack:
17402       case TwoMachinesPlay:
17403       case IcsPlayingWhite:
17404       case IcsPlayingBlack:
17405       case AnalyzeMode:
17406       case AnalyzeFile:
17407         /* Skip if we know it isn't thinking */
17408         if (!cps->maybeThinking) return;
17409         if (appData.debugMode)
17410           fprintf(debugFP, "Interrupting %s\n", cps->which);
17411         InterruptChildProcess(cps->pr);
17412         cps->maybeThinking = FALSE;
17413         break;
17414       default:
17415         break;
17416     }
17417 #endif /*ATTENTION*/
17418 }
17419
17420 int
17421 CheckFlags ()
17422 {
17423     if (whiteTimeRemaining <= 0) {
17424         if (!whiteFlag) {
17425             whiteFlag = TRUE;
17426             if (appData.icsActive) {
17427                 if (appData.autoCallFlag &&
17428                     gameMode == IcsPlayingBlack && !blackFlag) {
17429                   SendToICS(ics_prefix);
17430                   SendToICS("flag\n");
17431                 }
17432             } else {
17433                 if (blackFlag) {
17434                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17435                 } else {
17436                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17437                     if (appData.autoCallFlag) {
17438                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17439                         return TRUE;
17440                     }
17441                 }
17442             }
17443         }
17444     }
17445     if (blackTimeRemaining <= 0) {
17446         if (!blackFlag) {
17447             blackFlag = TRUE;
17448             if (appData.icsActive) {
17449                 if (appData.autoCallFlag &&
17450                     gameMode == IcsPlayingWhite && !whiteFlag) {
17451                   SendToICS(ics_prefix);
17452                   SendToICS("flag\n");
17453                 }
17454             } else {
17455                 if (whiteFlag) {
17456                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17457                 } else {
17458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17459                     if (appData.autoCallFlag) {
17460                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17461                         return TRUE;
17462                     }
17463                 }
17464             }
17465         }
17466     }
17467     return FALSE;
17468 }
17469
17470 void
17471 CheckTimeControl ()
17472 {
17473     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17474         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17475
17476     /*
17477      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17478      */
17479     if ( !WhiteOnMove(forwardMostMove) ) {
17480         /* White made time control */
17481         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17482         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17483         /* [HGM] time odds: correct new time quota for time odds! */
17484                                             / WhitePlayer()->timeOdds;
17485         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17486     } else {
17487         lastBlack -= blackTimeRemaining;
17488         /* Black made time control */
17489         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17490                                             / WhitePlayer()->other->timeOdds;
17491         lastWhite = whiteTimeRemaining;
17492     }
17493 }
17494
17495 void
17496 DisplayBothClocks ()
17497 {
17498     int wom = gameMode == EditPosition ?
17499       !blackPlaysFirst : WhiteOnMove(currentMove);
17500     DisplayWhiteClock(whiteTimeRemaining, wom);
17501     DisplayBlackClock(blackTimeRemaining, !wom);
17502 }
17503
17504
17505 /* Timekeeping seems to be a portability nightmare.  I think everyone
17506    has ftime(), but I'm really not sure, so I'm including some ifdefs
17507    to use other calls if you don't.  Clocks will be less accurate if
17508    you have neither ftime nor gettimeofday.
17509 */
17510
17511 /* VS 2008 requires the #include outside of the function */
17512 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17513 #include <sys/timeb.h>
17514 #endif
17515
17516 /* Get the current time as a TimeMark */
17517 void
17518 GetTimeMark (TimeMark *tm)
17519 {
17520 #if HAVE_GETTIMEOFDAY
17521
17522     struct timeval timeVal;
17523     struct timezone timeZone;
17524
17525     gettimeofday(&timeVal, &timeZone);
17526     tm->sec = (long) timeVal.tv_sec;
17527     tm->ms = (int) (timeVal.tv_usec / 1000L);
17528
17529 #else /*!HAVE_GETTIMEOFDAY*/
17530 #if HAVE_FTIME
17531
17532 // include <sys/timeb.h> / moved to just above start of function
17533     struct timeb timeB;
17534
17535     ftime(&timeB);
17536     tm->sec = (long) timeB.time;
17537     tm->ms = (int) timeB.millitm;
17538
17539 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17540     tm->sec = (long) time(NULL);
17541     tm->ms = 0;
17542 #endif
17543 #endif
17544 }
17545
17546 /* Return the difference in milliseconds between two
17547    time marks.  We assume the difference will fit in a long!
17548 */
17549 long
17550 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17551 {
17552     return 1000L*(tm2->sec - tm1->sec) +
17553            (long) (tm2->ms - tm1->ms);
17554 }
17555
17556
17557 /*
17558  * Code to manage the game clocks.
17559  *
17560  * In tournament play, black starts the clock and then white makes a move.
17561  * We give the human user a slight advantage if he is playing white---the
17562  * clocks don't run until he makes his first move, so it takes zero time.
17563  * Also, we don't account for network lag, so we could get out of sync
17564  * with GNU Chess's clock -- but then, referees are always right.
17565  */
17566
17567 static TimeMark tickStartTM;
17568 static long intendedTickLength;
17569
17570 long
17571 NextTickLength (long timeRemaining)
17572 {
17573     long nominalTickLength, nextTickLength;
17574
17575     if (timeRemaining > 0L && timeRemaining <= 10000L)
17576       nominalTickLength = 100L;
17577     else
17578       nominalTickLength = 1000L;
17579     nextTickLength = timeRemaining % nominalTickLength;
17580     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17581
17582     return nextTickLength;
17583 }
17584
17585 /* Adjust clock one minute up or down */
17586 void
17587 AdjustClock (Boolean which, int dir)
17588 {
17589     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17590     if(which) blackTimeRemaining += 60000*dir;
17591     else      whiteTimeRemaining += 60000*dir;
17592     DisplayBothClocks();
17593     adjustedClock = TRUE;
17594 }
17595
17596 /* Stop clocks and reset to a fresh time control */
17597 void
17598 ResetClocks ()
17599 {
17600     (void) StopClockTimer();
17601     if (appData.icsActive) {
17602         whiteTimeRemaining = blackTimeRemaining = 0;
17603     } else if (searchTime) {
17604         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17605         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17606     } else { /* [HGM] correct new time quote for time odds */
17607         whiteTC = blackTC = fullTimeControlString;
17608         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17609         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17610     }
17611     if (whiteFlag || blackFlag) {
17612         DisplayTitle("");
17613         whiteFlag = blackFlag = FALSE;
17614     }
17615     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17616     DisplayBothClocks();
17617     adjustedClock = FALSE;
17618 }
17619
17620 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17621
17622 /* Decrement running clock by amount of time that has passed */
17623 void
17624 DecrementClocks ()
17625 {
17626     long timeRemaining;
17627     long lastTickLength, fudge;
17628     TimeMark now;
17629
17630     if (!appData.clockMode) return;
17631     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17632
17633     GetTimeMark(&now);
17634
17635     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17636
17637     /* Fudge if we woke up a little too soon */
17638     fudge = intendedTickLength - lastTickLength;
17639     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17640
17641     if (WhiteOnMove(forwardMostMove)) {
17642         if(whiteNPS >= 0) lastTickLength = 0;
17643         timeRemaining = whiteTimeRemaining -= lastTickLength;
17644         if(timeRemaining < 0 && !appData.icsActive) {
17645             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17646             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17647                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17648                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17649             }
17650         }
17651         DisplayWhiteClock(whiteTimeRemaining - fudge,
17652                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17653     } else {
17654         if(blackNPS >= 0) lastTickLength = 0;
17655         timeRemaining = blackTimeRemaining -= lastTickLength;
17656         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17657             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17658             if(suddenDeath) {
17659                 blackStartMove = forwardMostMove;
17660                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17661             }
17662         }
17663         DisplayBlackClock(blackTimeRemaining - fudge,
17664                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17665     }
17666     if (CheckFlags()) return;
17667
17668     if(twoBoards) { // count down secondary board's clocks as well
17669         activePartnerTime -= lastTickLength;
17670         partnerUp = 1;
17671         if(activePartner == 'W')
17672             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17673         else
17674             DisplayBlackClock(activePartnerTime, TRUE);
17675         partnerUp = 0;
17676     }
17677
17678     tickStartTM = now;
17679     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17680     StartClockTimer(intendedTickLength);
17681
17682     /* if the time remaining has fallen below the alarm threshold, sound the
17683      * alarm. if the alarm has sounded and (due to a takeback or time control
17684      * with increment) the time remaining has increased to a level above the
17685      * threshold, reset the alarm so it can sound again.
17686      */
17687
17688     if (appData.icsActive && appData.icsAlarm) {
17689
17690         /* make sure we are dealing with the user's clock */
17691         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17692                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17693            )) return;
17694
17695         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17696             alarmSounded = FALSE;
17697         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17698             PlayAlarmSound();
17699             alarmSounded = TRUE;
17700         }
17701     }
17702 }
17703
17704
17705 /* A player has just moved, so stop the previously running
17706    clock and (if in clock mode) start the other one.
17707    We redisplay both clocks in case we're in ICS mode, because
17708    ICS gives us an update to both clocks after every move.
17709    Note that this routine is called *after* forwardMostMove
17710    is updated, so the last fractional tick must be subtracted
17711    from the color that is *not* on move now.
17712 */
17713 void
17714 SwitchClocks (int newMoveNr)
17715 {
17716     long lastTickLength;
17717     TimeMark now;
17718     int flagged = FALSE;
17719
17720     GetTimeMark(&now);
17721
17722     if (StopClockTimer() && appData.clockMode) {
17723         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17724         if (!WhiteOnMove(forwardMostMove)) {
17725             if(blackNPS >= 0) lastTickLength = 0;
17726             blackTimeRemaining -= lastTickLength;
17727            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17728 //         if(pvInfoList[forwardMostMove].time == -1)
17729                  pvInfoList[forwardMostMove].time =               // use GUI time
17730                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17731         } else {
17732            if(whiteNPS >= 0) lastTickLength = 0;
17733            whiteTimeRemaining -= lastTickLength;
17734            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17735 //         if(pvInfoList[forwardMostMove].time == -1)
17736                  pvInfoList[forwardMostMove].time =
17737                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17738         }
17739         flagged = CheckFlags();
17740     }
17741     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17742     CheckTimeControl();
17743
17744     if (flagged || !appData.clockMode) return;
17745
17746     switch (gameMode) {
17747       case MachinePlaysBlack:
17748       case MachinePlaysWhite:
17749       case BeginningOfGame:
17750         if (pausing) return;
17751         break;
17752
17753       case EditGame:
17754       case PlayFromGameFile:
17755       case IcsExamining:
17756         return;
17757
17758       default:
17759         break;
17760     }
17761
17762     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17763         if(WhiteOnMove(forwardMostMove))
17764              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17765         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17766     }
17767
17768     tickStartTM = now;
17769     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17770       whiteTimeRemaining : blackTimeRemaining);
17771     StartClockTimer(intendedTickLength);
17772 }
17773
17774
17775 /* Stop both clocks */
17776 void
17777 StopClocks ()
17778 {
17779     long lastTickLength;
17780     TimeMark now;
17781
17782     if (!StopClockTimer()) return;
17783     if (!appData.clockMode) return;
17784
17785     GetTimeMark(&now);
17786
17787     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17788     if (WhiteOnMove(forwardMostMove)) {
17789         if(whiteNPS >= 0) lastTickLength = 0;
17790         whiteTimeRemaining -= lastTickLength;
17791         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17792     } else {
17793         if(blackNPS >= 0) lastTickLength = 0;
17794         blackTimeRemaining -= lastTickLength;
17795         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17796     }
17797     CheckFlags();
17798 }
17799
17800 /* Start clock of player on move.  Time may have been reset, so
17801    if clock is already running, stop and restart it. */
17802 void
17803 StartClocks ()
17804 {
17805     (void) StopClockTimer(); /* in case it was running already */
17806     DisplayBothClocks();
17807     if (CheckFlags()) return;
17808
17809     if (!appData.clockMode) return;
17810     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17811
17812     GetTimeMark(&tickStartTM);
17813     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17814       whiteTimeRemaining : blackTimeRemaining);
17815
17816    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17817     whiteNPS = blackNPS = -1;
17818     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17819        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17820         whiteNPS = first.nps;
17821     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17822        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17823         blackNPS = first.nps;
17824     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17825         whiteNPS = second.nps;
17826     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17827         blackNPS = second.nps;
17828     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17829
17830     StartClockTimer(intendedTickLength);
17831 }
17832
17833 char *
17834 TimeString (long ms)
17835 {
17836     long second, minute, hour, day;
17837     char *sign = "";
17838     static char buf[32];
17839
17840     if (ms > 0 && ms <= 9900) {
17841       /* convert milliseconds to tenths, rounding up */
17842       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17843
17844       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17845       return buf;
17846     }
17847
17848     /* convert milliseconds to seconds, rounding up */
17849     /* use floating point to avoid strangeness of integer division
17850        with negative dividends on many machines */
17851     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17852
17853     if (second < 0) {
17854         sign = "-";
17855         second = -second;
17856     }
17857
17858     day = second / (60 * 60 * 24);
17859     second = second % (60 * 60 * 24);
17860     hour = second / (60 * 60);
17861     second = second % (60 * 60);
17862     minute = second / 60;
17863     second = second % 60;
17864
17865     if (day > 0)
17866       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17867               sign, day, hour, minute, second);
17868     else if (hour > 0)
17869       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17870     else
17871       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17872
17873     return buf;
17874 }
17875
17876
17877 /*
17878  * This is necessary because some C libraries aren't ANSI C compliant yet.
17879  */
17880 char *
17881 StrStr (char *string, char *match)
17882 {
17883     int i, length;
17884
17885     length = strlen(match);
17886
17887     for (i = strlen(string) - length; i >= 0; i--, string++)
17888       if (!strncmp(match, string, length))
17889         return string;
17890
17891     return NULL;
17892 }
17893
17894 char *
17895 StrCaseStr (char *string, char *match)
17896 {
17897     int i, j, length;
17898
17899     length = strlen(match);
17900
17901     for (i = strlen(string) - length; i >= 0; i--, string++) {
17902         for (j = 0; j < length; j++) {
17903             if (ToLower(match[j]) != ToLower(string[j]))
17904               break;
17905         }
17906         if (j == length) return string;
17907     }
17908
17909     return NULL;
17910 }
17911
17912 #ifndef _amigados
17913 int
17914 StrCaseCmp (char *s1, char *s2)
17915 {
17916     char c1, c2;
17917
17918     for (;;) {
17919         c1 = ToLower(*s1++);
17920         c2 = ToLower(*s2++);
17921         if (c1 > c2) return 1;
17922         if (c1 < c2) return -1;
17923         if (c1 == NULLCHAR) return 0;
17924     }
17925 }
17926
17927
17928 int
17929 ToLower (int c)
17930 {
17931     return isupper(c) ? tolower(c) : c;
17932 }
17933
17934
17935 int
17936 ToUpper (int c)
17937 {
17938     return islower(c) ? toupper(c) : c;
17939 }
17940 #endif /* !_amigados    */
17941
17942 char *
17943 StrSave (char *s)
17944 {
17945   char *ret;
17946
17947   if ((ret = (char *) malloc(strlen(s) + 1)))
17948     {
17949       safeStrCpy(ret, s, strlen(s)+1);
17950     }
17951   return ret;
17952 }
17953
17954 char *
17955 StrSavePtr (char *s, char **savePtr)
17956 {
17957     if (*savePtr) {
17958         free(*savePtr);
17959     }
17960     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17961       safeStrCpy(*savePtr, s, strlen(s)+1);
17962     }
17963     return(*savePtr);
17964 }
17965
17966 char *
17967 PGNDate ()
17968 {
17969     time_t clock;
17970     struct tm *tm;
17971     char buf[MSG_SIZ];
17972
17973     clock = time((time_t *)NULL);
17974     tm = localtime(&clock);
17975     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17976             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17977     return StrSave(buf);
17978 }
17979
17980
17981 char *
17982 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17983 {
17984     int i, j, fromX, fromY, toX, toY;
17985     int whiteToPlay, haveRights = nrCastlingRights;
17986     char buf[MSG_SIZ];
17987     char *p, *q;
17988     int emptycount;
17989     ChessSquare piece;
17990
17991     whiteToPlay = (gameMode == EditPosition) ?
17992       !blackPlaysFirst : (move % 2 == 0);
17993     p = buf;
17994
17995     /* Piece placement data */
17996     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17997         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17998         emptycount = 0;
17999         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18000             if (boards[move][i][j] == EmptySquare) {
18001                 emptycount++;
18002             } else { ChessSquare piece = boards[move][i][j];
18003                 if (emptycount > 0) {
18004                     if(emptycount<10) /* [HGM] can be >= 10 */
18005                         *p++ = '0' + emptycount;
18006                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18007                     emptycount = 0;
18008                 }
18009                 if(PieceToChar(piece) == '+') {
18010                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18011                     *p++ = '+';
18012                     piece = (ChessSquare)(CHUDEMOTED(piece));
18013                 }
18014                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18015                 if(*p = PieceSuffix(piece)) p++;
18016                 if(p[-1] == '~') {
18017                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18018                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18019                     *p++ = '~';
18020                 }
18021             }
18022         }
18023         if (emptycount > 0) {
18024             if(emptycount<10) /* [HGM] can be >= 10 */
18025                 *p++ = '0' + emptycount;
18026             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18027             emptycount = 0;
18028         }
18029         *p++ = '/';
18030     }
18031     *(p - 1) = ' ';
18032
18033     /* [HGM] print Crazyhouse or Shogi holdings */
18034     if( gameInfo.holdingsWidth ) {
18035         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18036         q = p;
18037         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18038             piece = boards[move][i][BOARD_WIDTH-1];
18039             if( piece != EmptySquare )
18040               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18041                   *p++ = PieceToChar(piece);
18042         }
18043         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18044             piece = boards[move][BOARD_HEIGHT-i-1][0];
18045             if( piece != EmptySquare )
18046               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18047                   *p++ = PieceToChar(piece);
18048         }
18049
18050         if( q == p ) *p++ = '-';
18051         *p++ = ']';
18052         *p++ = ' ';
18053     }
18054
18055     /* Active color */
18056     *p++ = whiteToPlay ? 'w' : 'b';
18057     *p++ = ' ';
18058
18059   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18060     haveRights = 0; q = p;
18061     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18062       piece = boards[move][0][i];
18063       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18064         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18065       }
18066     }
18067     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18068       piece = boards[move][BOARD_HEIGHT-1][i];
18069       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18070         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18071       }
18072     }
18073     if(p == q) *p++ = '-';
18074     *p++ = ' ';
18075   }
18076
18077   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18078     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18079   } else {
18080   if(haveRights) {
18081      int handW=0, handB=0;
18082      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18083         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18084         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18085      }
18086      q = p;
18087      if(appData.fischerCastling) {
18088         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18089            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18090                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18091         } else {
18092        /* [HGM] write directly from rights */
18093            if(boards[move][CASTLING][2] != NoRights &&
18094               boards[move][CASTLING][0] != NoRights   )
18095                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18096            if(boards[move][CASTLING][2] != NoRights &&
18097               boards[move][CASTLING][1] != NoRights   )
18098                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18099         }
18100         if(handB) {
18101            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18102                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18103         } else {
18104            if(boards[move][CASTLING][5] != NoRights &&
18105               boards[move][CASTLING][3] != NoRights   )
18106                 *p++ = boards[move][CASTLING][3] + AAA;
18107            if(boards[move][CASTLING][5] != NoRights &&
18108               boards[move][CASTLING][4] != NoRights   )
18109                 *p++ = boards[move][CASTLING][4] + AAA;
18110         }
18111      } else {
18112
18113         /* [HGM] write true castling rights */
18114         if( nrCastlingRights == 6 ) {
18115             int q, k=0;
18116             if(boards[move][CASTLING][0] != NoRights &&
18117                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18118             q = (boards[move][CASTLING][1] != NoRights &&
18119                  boards[move][CASTLING][2] != NoRights  );
18120             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18121                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18122                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18123                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18124             }
18125             if(q) *p++ = 'Q';
18126             k = 0;
18127             if(boards[move][CASTLING][3] != NoRights &&
18128                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18129             q = (boards[move][CASTLING][4] != NoRights &&
18130                  boards[move][CASTLING][5] != NoRights  );
18131             if(handB) {
18132                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18133                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18134                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18135             }
18136             if(q) *p++ = 'q';
18137         }
18138      }
18139      if (q == p) *p++ = '-'; /* No castling rights */
18140      *p++ = ' ';
18141   }
18142
18143   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18144      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18145      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18146     /* En passant target square */
18147     if (move > backwardMostMove) {
18148         fromX = moveList[move - 1][0] - AAA;
18149         fromY = moveList[move - 1][1] - ONE;
18150         toX = moveList[move - 1][2] - AAA;
18151         toY = moveList[move - 1][3] - ONE;
18152         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18153             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18154             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18155             fromX == toX) {
18156             /* 2-square pawn move just happened */
18157             *p++ = toX + AAA;
18158             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18159         } else {
18160             *p++ = '-';
18161         }
18162     } else if(move == backwardMostMove) {
18163         // [HGM] perhaps we should always do it like this, and forget the above?
18164         if((signed char)boards[move][EP_STATUS] >= 0) {
18165             *p++ = boards[move][EP_STATUS] + AAA;
18166             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18167         } else {
18168             *p++ = '-';
18169         }
18170     } else {
18171         *p++ = '-';
18172     }
18173     *p++ = ' ';
18174   }
18175   }
18176
18177     if(moveCounts)
18178     {   int i = 0, j=move;
18179
18180         /* [HGM] find reversible plies */
18181         if (appData.debugMode) { int k;
18182             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18183             for(k=backwardMostMove; k<=forwardMostMove; k++)
18184                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18185
18186         }
18187
18188         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18189         if( j == backwardMostMove ) i += initialRulePlies;
18190         sprintf(p, "%d ", i);
18191         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18192
18193         /* Fullmove number */
18194         sprintf(p, "%d", (move / 2) + 1);
18195     } else *--p = NULLCHAR;
18196
18197     return StrSave(buf);
18198 }
18199
18200 Boolean
18201 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18202 {
18203     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18204     char *p, c;
18205     int emptycount, virgin[BOARD_FILES];
18206     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18207
18208     p = fen;
18209
18210     /* Piece placement data */
18211     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18212         j = 0;
18213         for (;;) {
18214             if (*p == '/' || *p == ' ' || *p == '[' ) {
18215                 if(j > w) w = j;
18216                 emptycount = gameInfo.boardWidth - j;
18217                 while (emptycount--)
18218                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18219                 if (*p == '/') p++;
18220                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18221                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18222                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18223                     }
18224                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18225                 }
18226                 break;
18227 #if(BOARD_FILES >= 10)*0
18228             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18229                 p++; emptycount=10;
18230                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18231                 while (emptycount--)
18232                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18233 #endif
18234             } else if (*p == '*') {
18235                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18236             } else if (isdigit(*p)) {
18237                 emptycount = *p++ - '0';
18238                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18239                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18240                 while (emptycount--)
18241                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18242             } else if (*p == '<') {
18243                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18244                 else if (i != 0 || !shuffle) return FALSE;
18245                 p++;
18246             } else if (shuffle && *p == '>') {
18247                 p++; // for now ignore closing shuffle range, and assume rank-end
18248             } else if (*p == '?') {
18249                 if (j >= gameInfo.boardWidth) return FALSE;
18250                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18251                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18252             } else if (*p == '+' || isalpha(*p)) {
18253                 char *q, *s = SUFFIXES;
18254                 if (j >= gameInfo.boardWidth) return FALSE;
18255                 if(*p=='+') {
18256                     char c = *++p;
18257                     if(q = strchr(s, p[1])) p++;
18258                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18259                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18260                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18261                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18262                 } else {
18263                     char c = *p++;
18264                     if(q = strchr(s, *p)) p++;
18265                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18266                 }
18267
18268                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18269                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18270                     piece = (ChessSquare) (PROMOTED(piece));
18271                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18272                     p++;
18273                 }
18274                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18275                 if(piece == king) wKingRank = i;
18276                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18277             } else {
18278                 return FALSE;
18279             }
18280         }
18281     }
18282     while (*p == '/' || *p == ' ') p++;
18283
18284     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18285
18286     /* [HGM] by default clear Crazyhouse holdings, if present */
18287     if(gameInfo.holdingsWidth) {
18288        for(i=0; i<BOARD_HEIGHT; i++) {
18289            board[i][0]             = EmptySquare; /* black holdings */
18290            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18291            board[i][1]             = (ChessSquare) 0; /* black counts */
18292            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18293        }
18294     }
18295
18296     /* [HGM] look for Crazyhouse holdings here */
18297     while(*p==' ') p++;
18298     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18299         int swap=0, wcnt=0, bcnt=0;
18300         if(*p == '[') p++;
18301         if(*p == '<') swap++, p++;
18302         if(*p == '-' ) p++; /* empty holdings */ else {
18303             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18304             /* if we would allow FEN reading to set board size, we would   */
18305             /* have to add holdings and shift the board read so far here   */
18306             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18307                 p++;
18308                 if((int) piece >= (int) BlackPawn ) {
18309                     i = (int)piece - (int)BlackPawn;
18310                     i = PieceToNumber((ChessSquare)i);
18311                     if( i >= gameInfo.holdingsSize ) return FALSE;
18312                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18313                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18314                     bcnt++;
18315                 } else {
18316                     i = (int)piece - (int)WhitePawn;
18317                     i = PieceToNumber((ChessSquare)i);
18318                     if( i >= gameInfo.holdingsSize ) return FALSE;
18319                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18320                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18321                     wcnt++;
18322                 }
18323             }
18324             if(subst) { // substitute back-rank question marks by holdings pieces
18325                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18326                     int k, m, n = bcnt + 1;
18327                     if(board[0][j] == ClearBoard) {
18328                         if(!wcnt) return FALSE;
18329                         n = rand() % wcnt;
18330                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18331                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18332                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18333                             break;
18334                         }
18335                     }
18336                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18337                         if(!bcnt) return FALSE;
18338                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18339                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18340                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18341                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18342                             break;
18343                         }
18344                     }
18345                 }
18346                 subst = 0;
18347             }
18348         }
18349         if(*p == ']') p++;
18350     }
18351
18352     if(subst) return FALSE; // substitution requested, but no holdings
18353
18354     while(*p == ' ') p++;
18355
18356     /* Active color */
18357     c = *p++;
18358     if(appData.colorNickNames) {
18359       if( c == appData.colorNickNames[0] ) c = 'w'; else
18360       if( c == appData.colorNickNames[1] ) c = 'b';
18361     }
18362     switch (c) {
18363       case 'w':
18364         *blackPlaysFirst = FALSE;
18365         break;
18366       case 'b':
18367         *blackPlaysFirst = TRUE;
18368         break;
18369       default:
18370         return FALSE;
18371     }
18372
18373     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18374     /* return the extra info in global variiables             */
18375
18376     while(*p==' ') p++;
18377
18378     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18379         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18380         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18381     }
18382
18383     /* set defaults in case FEN is incomplete */
18384     board[EP_STATUS] = EP_UNKNOWN;
18385     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18386     for(i=0; i<nrCastlingRights; i++ ) {
18387         board[CASTLING][i] =
18388             appData.fischerCastling ? NoRights : initialRights[i];
18389     }   /* assume possible unless obviously impossible */
18390     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18391     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18392     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18393                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18394     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18395     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18396     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18397                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18398     FENrulePlies = 0;
18399
18400     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18401       char *q = p;
18402       int w=0, b=0;
18403       while(isalpha(*p)) {
18404         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18405         if(islower(*p)) b |= 1 << (*p++ - 'a');
18406       }
18407       if(*p == '-') p++;
18408       if(p != q) {
18409         board[TOUCHED_W] = ~w;
18410         board[TOUCHED_B] = ~b;
18411         while(*p == ' ') p++;
18412       }
18413     } else
18414
18415     if(nrCastlingRights) {
18416       int fischer = 0;
18417       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18418       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18419           /* castling indicator present, so default becomes no castlings */
18420           for(i=0; i<nrCastlingRights; i++ ) {
18421                  board[CASTLING][i] = NoRights;
18422           }
18423       }
18424       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18425              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18426              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18427              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18428         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18429
18430         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18431             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18432             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18433         }
18434         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18435             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18436         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18437                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18438         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18439                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18440         switch(c) {
18441           case'K':
18442               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18443               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18444               board[CASTLING][2] = whiteKingFile;
18445               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18446               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18447               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18448               break;
18449           case'Q':
18450               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18451               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18452               board[CASTLING][2] = whiteKingFile;
18453               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18454               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18455               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18456               break;
18457           case'k':
18458               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18459               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18460               board[CASTLING][5] = blackKingFile;
18461               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18462               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18463               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18464               break;
18465           case'q':
18466               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18467               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18468               board[CASTLING][5] = blackKingFile;
18469               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18470               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18471               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18472           case '-':
18473               break;
18474           default: /* FRC castlings */
18475               if(c >= 'a') { /* black rights */
18476                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18477                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18478                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18479                   if(i == BOARD_RGHT) break;
18480                   board[CASTLING][5] = i;
18481                   c -= AAA;
18482                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18483                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18484                   if(c > i)
18485                       board[CASTLING][3] = c;
18486                   else
18487                       board[CASTLING][4] = c;
18488               } else { /* white rights */
18489                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18490                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18491                     if(board[0][i] == WhiteKing) break;
18492                   if(i == BOARD_RGHT) break;
18493                   board[CASTLING][2] = i;
18494                   c -= AAA - 'a' + 'A';
18495                   if(board[0][c] >= WhiteKing) break;
18496                   if(c > i)
18497                       board[CASTLING][0] = c;
18498                   else
18499                       board[CASTLING][1] = c;
18500               }
18501         }
18502       }
18503       for(i=0; i<nrCastlingRights; i++)
18504         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18505       if(gameInfo.variant == VariantSChess)
18506         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18507       if(fischer && shuffle) appData.fischerCastling = TRUE;
18508     if (appData.debugMode) {
18509         fprintf(debugFP, "FEN castling rights:");
18510         for(i=0; i<nrCastlingRights; i++)
18511         fprintf(debugFP, " %d", board[CASTLING][i]);
18512         fprintf(debugFP, "\n");
18513     }
18514
18515       while(*p==' ') p++;
18516     }
18517
18518     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18519
18520     /* read e.p. field in games that know e.p. capture */
18521     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18522        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18523        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18524       if(*p=='-') {
18525         p++; board[EP_STATUS] = EP_NONE;
18526       } else {
18527          char c = *p++ - AAA;
18528
18529          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18530          if(*p >= '0' && *p <='9') p++;
18531          board[EP_STATUS] = c;
18532       }
18533     }
18534
18535
18536     if(sscanf(p, "%d", &i) == 1) {
18537         FENrulePlies = i; /* 50-move ply counter */
18538         /* (The move number is still ignored)    */
18539     }
18540
18541     return TRUE;
18542 }
18543
18544 void
18545 EditPositionPasteFEN (char *fen)
18546 {
18547   if (fen != NULL) {
18548     Board initial_position;
18549
18550     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18551       DisplayError(_("Bad FEN position in clipboard"), 0);
18552       return ;
18553     } else {
18554       int savedBlackPlaysFirst = blackPlaysFirst;
18555       EditPositionEvent();
18556       blackPlaysFirst = savedBlackPlaysFirst;
18557       CopyBoard(boards[0], initial_position);
18558       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18559       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18560       DisplayBothClocks();
18561       DrawPosition(FALSE, boards[currentMove]);
18562     }
18563   }
18564 }
18565
18566 static char cseq[12] = "\\   ";
18567
18568 Boolean
18569 set_cont_sequence (char *new_seq)
18570 {
18571     int len;
18572     Boolean ret;
18573
18574     // handle bad attempts to set the sequence
18575         if (!new_seq)
18576                 return 0; // acceptable error - no debug
18577
18578     len = strlen(new_seq);
18579     ret = (len > 0) && (len < sizeof(cseq));
18580     if (ret)
18581       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18582     else if (appData.debugMode)
18583       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18584     return ret;
18585 }
18586
18587 /*
18588     reformat a source message so words don't cross the width boundary.  internal
18589     newlines are not removed.  returns the wrapped size (no null character unless
18590     included in source message).  If dest is NULL, only calculate the size required
18591     for the dest buffer.  lp argument indicats line position upon entry, and it's
18592     passed back upon exit.
18593 */
18594 int
18595 wrap (char *dest, char *src, int count, int width, int *lp)
18596 {
18597     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18598
18599     cseq_len = strlen(cseq);
18600     old_line = line = *lp;
18601     ansi = len = clen = 0;
18602
18603     for (i=0; i < count; i++)
18604     {
18605         if (src[i] == '\033')
18606             ansi = 1;
18607
18608         // if we hit the width, back up
18609         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18610         {
18611             // store i & len in case the word is too long
18612             old_i = i, old_len = len;
18613
18614             // find the end of the last word
18615             while (i && src[i] != ' ' && src[i] != '\n')
18616             {
18617                 i--;
18618                 len--;
18619             }
18620
18621             // word too long?  restore i & len before splitting it
18622             if ((old_i-i+clen) >= width)
18623             {
18624                 i = old_i;
18625                 len = old_len;
18626             }
18627
18628             // extra space?
18629             if (i && src[i-1] == ' ')
18630                 len--;
18631
18632             if (src[i] != ' ' && src[i] != '\n')
18633             {
18634                 i--;
18635                 if (len)
18636                     len--;
18637             }
18638
18639             // now append the newline and continuation sequence
18640             if (dest)
18641                 dest[len] = '\n';
18642             len++;
18643             if (dest)
18644                 strncpy(dest+len, cseq, cseq_len);
18645             len += cseq_len;
18646             line = cseq_len;
18647             clen = cseq_len;
18648             continue;
18649         }
18650
18651         if (dest)
18652             dest[len] = src[i];
18653         len++;
18654         if (!ansi)
18655             line++;
18656         if (src[i] == '\n')
18657             line = 0;
18658         if (src[i] == 'm')
18659             ansi = 0;
18660     }
18661     if (dest && appData.debugMode)
18662     {
18663         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18664             count, width, line, len, *lp);
18665         show_bytes(debugFP, src, count);
18666         fprintf(debugFP, "\ndest: ");
18667         show_bytes(debugFP, dest, len);
18668         fprintf(debugFP, "\n");
18669     }
18670     *lp = dest ? line : old_line;
18671
18672     return len;
18673 }
18674
18675 // [HGM] vari: routines for shelving variations
18676 Boolean modeRestore = FALSE;
18677
18678 void
18679 PushInner (int firstMove, int lastMove)
18680 {
18681         int i, j, nrMoves = lastMove - firstMove;
18682
18683         // push current tail of game on stack
18684         savedResult[storedGames] = gameInfo.result;
18685         savedDetails[storedGames] = gameInfo.resultDetails;
18686         gameInfo.resultDetails = NULL;
18687         savedFirst[storedGames] = firstMove;
18688         savedLast [storedGames] = lastMove;
18689         savedFramePtr[storedGames] = framePtr;
18690         framePtr -= nrMoves; // reserve space for the boards
18691         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18692             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18693             for(j=0; j<MOVE_LEN; j++)
18694                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18695             for(j=0; j<2*MOVE_LEN; j++)
18696                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18697             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18698             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18699             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18700             pvInfoList[firstMove+i-1].depth = 0;
18701             commentList[framePtr+i] = commentList[firstMove+i];
18702             commentList[firstMove+i] = NULL;
18703         }
18704
18705         storedGames++;
18706         forwardMostMove = firstMove; // truncate game so we can start variation
18707 }
18708
18709 void
18710 PushTail (int firstMove, int lastMove)
18711 {
18712         if(appData.icsActive) { // only in local mode
18713                 forwardMostMove = currentMove; // mimic old ICS behavior
18714                 return;
18715         }
18716         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18717
18718         PushInner(firstMove, lastMove);
18719         if(storedGames == 1) GreyRevert(FALSE);
18720         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18721 }
18722
18723 void
18724 PopInner (Boolean annotate)
18725 {
18726         int i, j, nrMoves;
18727         char buf[8000], moveBuf[20];
18728
18729         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18730         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18731         nrMoves = savedLast[storedGames] - currentMove;
18732         if(annotate) {
18733                 int cnt = 10;
18734                 if(!WhiteOnMove(currentMove))
18735                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18736                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18737                 for(i=currentMove; i<forwardMostMove; i++) {
18738                         if(WhiteOnMove(i))
18739                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18740                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18741                         strcat(buf, moveBuf);
18742                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18743                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18744                 }
18745                 strcat(buf, ")");
18746         }
18747         for(i=1; i<=nrMoves; i++) { // copy last variation back
18748             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18749             for(j=0; j<MOVE_LEN; j++)
18750                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18751             for(j=0; j<2*MOVE_LEN; j++)
18752                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18753             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18754             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18755             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18756             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18757             commentList[currentMove+i] = commentList[framePtr+i];
18758             commentList[framePtr+i] = NULL;
18759         }
18760         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18761         framePtr = savedFramePtr[storedGames];
18762         gameInfo.result = savedResult[storedGames];
18763         if(gameInfo.resultDetails != NULL) {
18764             free(gameInfo.resultDetails);
18765       }
18766         gameInfo.resultDetails = savedDetails[storedGames];
18767         forwardMostMove = currentMove + nrMoves;
18768 }
18769
18770 Boolean
18771 PopTail (Boolean annotate)
18772 {
18773         if(appData.icsActive) return FALSE; // only in local mode
18774         if(!storedGames) return FALSE; // sanity
18775         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18776
18777         PopInner(annotate);
18778         if(currentMove < forwardMostMove) ForwardEvent(); else
18779         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18780
18781         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18782         return TRUE;
18783 }
18784
18785 void
18786 CleanupTail ()
18787 {       // remove all shelved variations
18788         int i;
18789         for(i=0; i<storedGames; i++) {
18790             if(savedDetails[i])
18791                 free(savedDetails[i]);
18792             savedDetails[i] = NULL;
18793         }
18794         for(i=framePtr; i<MAX_MOVES; i++) {
18795                 if(commentList[i]) free(commentList[i]);
18796                 commentList[i] = NULL;
18797         }
18798         framePtr = MAX_MOVES-1;
18799         storedGames = 0;
18800 }
18801
18802 void
18803 LoadVariation (int index, char *text)
18804 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18805         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18806         int level = 0, move;
18807
18808         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18809         // first find outermost bracketing variation
18810         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18811             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18812                 if(*p == '{') wait = '}'; else
18813                 if(*p == '[') wait = ']'; else
18814                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18815                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18816             }
18817             if(*p == wait) wait = NULLCHAR; // closing ]} found
18818             p++;
18819         }
18820         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18821         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18822         end[1] = NULLCHAR; // clip off comment beyond variation
18823         ToNrEvent(currentMove-1);
18824         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18825         // kludge: use ParsePV() to append variation to game
18826         move = currentMove;
18827         ParsePV(start, TRUE, TRUE);
18828         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18829         ClearPremoveHighlights();
18830         CommentPopDown();
18831         ToNrEvent(currentMove+1);
18832 }
18833
18834 void
18835 LoadTheme ()
18836 {
18837     char *p, *q, buf[MSG_SIZ];
18838     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18839         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18840         ParseArgsFromString(buf);
18841         ActivateTheme(TRUE); // also redo colors
18842         return;
18843     }
18844     p = nickName;
18845     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18846     {
18847         int len;
18848         q = appData.themeNames;
18849         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18850       if(appData.useBitmaps) {
18851         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18852                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18853                 appData.liteBackTextureMode,
18854                 appData.darkBackTextureMode );
18855       } else {
18856         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18857                 Col2Text(2),   // lightSquareColor
18858                 Col2Text(3) ); // darkSquareColor
18859       }
18860       if(appData.useBorder) {
18861         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18862                 appData.border);
18863       } else {
18864         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18865       }
18866       if(appData.useFont) {
18867         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18868                 appData.renderPiecesWithFont,
18869                 appData.fontToPieceTable,
18870                 Col2Text(9),    // appData.fontBackColorWhite
18871                 Col2Text(10) ); // appData.fontForeColorBlack
18872       } else {
18873         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18874                 appData.pieceDirectory);
18875         if(!appData.pieceDirectory[0])
18876           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18877                 Col2Text(0),   // whitePieceColor
18878                 Col2Text(1) ); // blackPieceColor
18879       }
18880       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18881                 Col2Text(4),   // highlightSquareColor
18882                 Col2Text(5) ); // premoveHighlightColor
18883         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18884         if(insert != q) insert[-1] = NULLCHAR;
18885         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18886         if(q)   free(q);
18887     }
18888     ActivateTheme(FALSE);
18889 }